@adia-ai/a2ui-compose 0.0.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.
@@ -0,0 +1,289 @@
1
+ /**
2
+ * PipelineEngine — State machine for generative pipeline executions.
3
+ *
4
+ * Manages execution lifecycle: start → stage submissions → completion/abort.
5
+ * Enforces stage ordering (interpret → analyze → plan → generate → validate → render)
6
+ * with doNotRedo support for skipping stages on iteration.
7
+ */
8
+
9
+ import { STAGES, stageIndex, createStepReport, createEvent } from './types.js';
10
+
11
+ let nextId = 1;
12
+
13
+ /** Generate a unique execution ID */
14
+ function uid() {
15
+ return `exec_${Date.now().toString(36)}_${(nextId++).toString(36)}`;
16
+ }
17
+
18
+ /**
19
+ * @typedef {object} PipelineExecution
20
+ * @property {string} id — Unique execution ID
21
+ * @property {string} intent — Original user intent
22
+ * @property {'instant'|'thinking'|'pro'|'god'} mode — Generation mode
23
+ * @property {'active'|'completed'|'failed'|'aborted'} state — Current state
24
+ * @property {Map<string, object>} stages — Stage outputs keyed by stage name
25
+ * @property {object[]} reports — StepReports in order
26
+ * @property {object[]} events — PipelineEvent log
27
+ * @property {string[]} doNotRedo — Stages to skip on iteration
28
+ * @property {number} created — Timestamp
29
+ * @property {number} updated — Timestamp
30
+ */
31
+
32
+ export class PipelineEngine {
33
+ #executions = new Map();
34
+
35
+ /**
36
+ * Start a new pipeline execution.
37
+ *
38
+ * @param {object} opts
39
+ * @param {string} opts.intent — User intent text
40
+ * @param {'instant'|'thinking'|'pro'|'god'} [opts.mode='instant'] — Generation mode
41
+ * @returns {string} — executionId
42
+ */
43
+ start({ intent, mode = 'instant' }) {
44
+ const id = uid();
45
+ const execution = {
46
+ id,
47
+ intent,
48
+ mode,
49
+ state: 'active',
50
+ stages: new Map(),
51
+ reports: [],
52
+ events: [],
53
+ doNotRedo: [],
54
+ created: Date.now(),
55
+ updated: Date.now(),
56
+ };
57
+
58
+ execution.events.push(createEvent({
59
+ executionId: id,
60
+ type: 'pipeline:start',
61
+ message: `Pipeline started for intent: "${intent}" (mode: ${mode})`,
62
+ data: { intent, mode },
63
+ }));
64
+
65
+ this.#executions.set(id, execution);
66
+ return id;
67
+ }
68
+
69
+ /**
70
+ * Submit a stage output to an active execution.
71
+ *
72
+ * Validates stage ordering: stages must proceed in STAGES order.
73
+ * Stages listed in doNotRedo are skipped automatically.
74
+ *
75
+ * @param {string} executionId
76
+ * @param {string} stage — Stage name (from STAGES)
77
+ * @param {object} output — Stage output data
78
+ * @param {object} [opts] — Options
79
+ * @param {boolean} [opts.force=false] — Skip ordering check
80
+ * @param {string[]} [opts.doNotRedo] — Stages to skip on future iterations
81
+ * @returns {object} — StepReport
82
+ */
83
+ submitStage(executionId, stage, output, { force = false, doNotRedo = [] } = {}) {
84
+ const execution = this.#executions.get(executionId);
85
+ if (!execution) throw new Error(`Unknown execution: ${executionId}`);
86
+ if (execution.state !== 'active') throw new Error(`Execution ${executionId} is ${execution.state}, not active`);
87
+
88
+ const idx = stageIndex(stage);
89
+ if (idx === -1) throw new Error(`Unknown stage: ${stage}`);
90
+
91
+ // Check ordering: the submitted stage must come after the last completed stage
92
+ if (!force && execution.stages.size > 0) {
93
+ const completedStages = [...execution.stages.keys()];
94
+ const lastCompleted = completedStages[completedStages.length - 1];
95
+ const lastIdx = stageIndex(lastCompleted);
96
+
97
+ // Allow re-submission of same stage (iteration) or next stage
98
+ if (idx < lastIdx) {
99
+ throw new Error(`Stage "${stage}" (${idx}) cannot precede already completed "${lastCompleted}" (${lastIdx}). Use force to override.`);
100
+ }
101
+
102
+ // Check for skipped stages (unless they're in doNotRedo)
103
+ const allSkippable = [...execution.doNotRedo, ...doNotRedo];
104
+ for (let i = lastIdx + 1; i < idx; i++) {
105
+ const skippedName = STAGES[i];
106
+ if (!allSkippable.includes(skippedName) && !execution.stages.has(skippedName)) {
107
+ throw new Error(`Stage "${skippedName}" must be completed before "${stage}". Add to doNotRedo to skip.`);
108
+ }
109
+ }
110
+ }
111
+
112
+ // Record doNotRedo additions
113
+ if (doNotRedo.length > 0) {
114
+ for (const s of doNotRedo) {
115
+ if (!execution.doNotRedo.includes(s)) execution.doNotRedo.push(s);
116
+ }
117
+ }
118
+
119
+ // Store stage output
120
+ execution.stages.set(stage, output);
121
+ execution.updated = Date.now();
122
+
123
+ // Create step report
124
+ const report = createStepReport({
125
+ stage,
126
+ status: 'passed',
127
+ goal: `Complete ${stage} stage`,
128
+ summary: `Stage "${stage}" submitted successfully`,
129
+ confidence: output.confidence ?? 0.8,
130
+ checks: output.checks ?? [],
131
+ assumptions: output.assumptions ?? [],
132
+ });
133
+
134
+ execution.reports.push(report);
135
+
136
+ // Log event
137
+ execution.events.push(createEvent({
138
+ executionId,
139
+ type: 'stage:complete',
140
+ stage,
141
+ message: `Stage "${stage}" completed`,
142
+ data: { output: summarizeOutput(output) },
143
+ }));
144
+
145
+ // Auto-complete if render stage is done
146
+ if (stage === 'render') {
147
+ execution.state = 'completed';
148
+ execution.events.push(createEvent({
149
+ executionId,
150
+ type: 'pipeline:complete',
151
+ message: 'Pipeline completed successfully',
152
+ }));
153
+ }
154
+
155
+ return report;
156
+ }
157
+
158
+ /**
159
+ * Get the current state of an execution.
160
+ *
161
+ * @param {string} executionId
162
+ * @returns {object|null} — Execution state snapshot (or null if not found)
163
+ */
164
+ getState(executionId) {
165
+ const execution = this.#executions.get(executionId);
166
+ if (!execution) return null;
167
+
168
+ return {
169
+ id: execution.id,
170
+ intent: execution.intent,
171
+ mode: execution.mode,
172
+ state: execution.state,
173
+ completedStages: [...execution.stages.keys()],
174
+ currentStage: this.#currentStage(execution),
175
+ reports: [...execution.reports],
176
+ eventCount: execution.events.length,
177
+ doNotRedo: [...execution.doNotRedo],
178
+ created: execution.created,
179
+ updated: execution.updated,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Get the full event log for an execution.
185
+ *
186
+ * @param {string} executionId
187
+ * @returns {object[]}
188
+ */
189
+ getEvents(executionId) {
190
+ const execution = this.#executions.get(executionId);
191
+ return execution ? [...execution.events] : [];
192
+ }
193
+
194
+ /**
195
+ * Get the output from a specific stage.
196
+ *
197
+ * @param {string} executionId
198
+ * @param {string} stage
199
+ * @returns {object|null}
200
+ */
201
+ getStageOutput(executionId, stage) {
202
+ const execution = this.#executions.get(executionId);
203
+ if (!execution) return null;
204
+ return execution.stages.get(stage) ?? null;
205
+ }
206
+
207
+ /**
208
+ * Abort an active execution.
209
+ *
210
+ * @param {string} executionId
211
+ * @param {string} [reason] — Abort reason
212
+ */
213
+ abort(executionId, reason = 'Aborted by caller') {
214
+ const execution = this.#executions.get(executionId);
215
+ if (!execution) throw new Error(`Unknown execution: ${executionId}`);
216
+ if (execution.state !== 'active') return; // already terminal
217
+
218
+ execution.state = 'aborted';
219
+ execution.updated = Date.now();
220
+
221
+ execution.events.push(createEvent({
222
+ executionId,
223
+ type: 'pipeline:abort',
224
+ message: reason,
225
+ }));
226
+ }
227
+
228
+ /**
229
+ * Mark an execution as failed.
230
+ *
231
+ * @param {string} executionId
232
+ * @param {string} stage — Stage where failure occurred
233
+ * @param {string} error — Error description
234
+ */
235
+ fail(executionId, stage, error) {
236
+ const execution = this.#executions.get(executionId);
237
+ if (!execution) throw new Error(`Unknown execution: ${executionId}`);
238
+ if (execution.state !== 'active') return;
239
+
240
+ execution.state = 'failed';
241
+ execution.updated = Date.now();
242
+
243
+ const report = createStepReport({
244
+ stage,
245
+ status: 'failed',
246
+ goal: `Complete ${stage} stage`,
247
+ summary: error,
248
+ confidence: 0,
249
+ });
250
+ execution.reports.push(report);
251
+
252
+ execution.events.push(createEvent({
253
+ executionId,
254
+ type: 'stage:fail',
255
+ stage,
256
+ message: error,
257
+ }));
258
+
259
+ execution.events.push(createEvent({
260
+ executionId,
261
+ type: 'pipeline:fail',
262
+ message: `Pipeline failed at stage "${stage}": ${error}`,
263
+ }));
264
+ }
265
+
266
+ /**
267
+ * Determine the next expected stage for an execution.
268
+ * @param {object} execution
269
+ * @returns {string|null}
270
+ */
271
+ #currentStage(execution) {
272
+ if (execution.state !== 'active') return null;
273
+ const completed = new Set(execution.stages.keys());
274
+ for (const stage of STAGES) {
275
+ if (!completed.has(stage) && !execution.doNotRedo.includes(stage)) {
276
+ return stage;
277
+ }
278
+ }
279
+ return null;
280
+ }
281
+ }
282
+
283
+ /** Summarize output for event log (avoid storing huge payloads in events) */
284
+ function summarizeOutput(output) {
285
+ if (!output) return null;
286
+ const keys = Object.keys(output);
287
+ if (keys.length <= 5) return output;
288
+ return { _keys: keys, _size: JSON.stringify(output).length };
289
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Pipeline Type Definitions — Stage names, StepReport, Handoff, PipelineEvent.
3
+ *
4
+ * Pure data constructors for the pipeline state machine.
5
+ * No dependencies — usable from browser and Node.
6
+ */
7
+
8
+ /** Ordered pipeline stages */
9
+ export const STAGES = ['interpret', 'analyze', 'plan', 'generate', 'validate', 'render'];
10
+
11
+ /** Stage index lookup for ordering enforcement */
12
+ const STAGE_INDEX = Object.fromEntries(STAGES.map((s, i) => [s, i]));
13
+
14
+ /**
15
+ * Get the numeric index of a stage (for ordering checks).
16
+ * @param {string} stage
17
+ * @returns {number} — -1 if unknown
18
+ */
19
+ export function stageIndex(stage) {
20
+ return STAGE_INDEX[stage] ?? -1;
21
+ }
22
+
23
+ /**
24
+ * Create a StepReport — per-stage quality gate.
25
+ *
26
+ * @param {object} opts
27
+ * @param {string} opts.stage — Stage name (from STAGES)
28
+ * @param {'passed'|'failed'|'skipped'} opts.status — Gate result
29
+ * @param {string} opts.goal — What this stage aimed to accomplish
30
+ * @param {string} opts.summary — What actually happened
31
+ * @param {number} opts.confidence — 0-1 confidence score
32
+ * @param {object[]} [opts.checks] — Individual check results
33
+ * @param {string[]} [opts.assumptions] — Assumptions made during this stage
34
+ * @returns {object}
35
+ */
36
+ export function createStepReport({ stage, status, goal, summary, confidence, checks = [], assumptions = [] }) {
37
+ return {
38
+ stage,
39
+ status,
40
+ goal,
41
+ summary,
42
+ confidence: Math.max(0, Math.min(1, confidence)),
43
+ checks,
44
+ assumptions,
45
+ timestamp: Date.now(),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Create a Handoff — stage boundary contract.
51
+ *
52
+ * @param {object} opts
53
+ * @param {string} opts.from — Source stage
54
+ * @param {string} opts.to — Target stage
55
+ * @param {object} opts.completed — Data produced by the source stage
56
+ * @param {string[]} [opts.unresolved] — Open questions or gaps
57
+ * @param {string[]} [opts.doNotRedo] — Stages that should be skipped on iteration
58
+ * @returns {object}
59
+ */
60
+ export function createHandoff({ from, to, completed, unresolved = [], doNotRedo = [] }) {
61
+ return {
62
+ from,
63
+ to,
64
+ completed,
65
+ unresolved,
66
+ doNotRedo,
67
+ timestamp: Date.now(),
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Create a PipelineEvent — event log entry.
73
+ *
74
+ * @param {object} opts
75
+ * @param {string} opts.executionId
76
+ * @param {'stage:start'|'stage:complete'|'stage:fail'|'stage:skip'|'pipeline:start'|'pipeline:complete'|'pipeline:fail'|'pipeline:abort'} opts.type
77
+ * @param {string} [opts.stage] — Stage name (if stage-level event)
78
+ * @param {string} opts.message — Human-readable description
79
+ * @param {*} [opts.data] — Arbitrary event payload
80
+ * @returns {object}
81
+ */
82
+ export function createEvent({ executionId, type, stage = null, message, data = null }) {
83
+ return {
84
+ executionId,
85
+ type,
86
+ stage,
87
+ message,
88
+ data,
89
+ timestamp: Date.now(),
90
+ };
91
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Reference Tools — Delegates to A003 intelligence system.
3
+ *
4
+ * Thin wrappers that provide a stable generative-system API surface
5
+ * over the intelligence module. Used by the generator and pipeline stages
6
+ * to look up components, patterns, and domain context.
7
+ */
8
+
9
+ import { getCatalog, getComponent, getComponentsByCategory } from '../../retrieval/index.js';
10
+ import { classifyIntent, assembleContext } from '../../retrieval/index.js';
11
+ import { getAllPatterns, searchPatterns, getPattern, semanticSearchPatterns } from '../../retrieval/index.js';
12
+ import { checkAllAntiPatterns } from '../../retrieval/index.js';
13
+ import { serializeEntry } from '../../retrieval/index.js';
14
+
15
+ /**
16
+ * Look up a single component by A2UI type name.
17
+ * @param {string} typeName — e.g. 'Button', 'Card', 'TextField'
18
+ * @returns {object|null}
19
+ */
20
+ export async function lookupComponent(typeName) {
21
+ return getComponent(typeName);
22
+ }
23
+
24
+ /**
25
+ * Get reference-level entry for a component (full API surface).
26
+ * @param {string} typeName
27
+ * @returns {object|null}
28
+ */
29
+ export async function getComponentAPI(typeName) {
30
+ const entry = await getComponent(typeName);
31
+ if (!entry) return null;
32
+ return serializeEntry(entry, 'reference');
33
+ }
34
+
35
+ /**
36
+ * Get the full component catalog map.
37
+ * @returns {object}
38
+ */
39
+ export async function getComponentMap() {
40
+ return getCatalog();
41
+ }
42
+
43
+ /**
44
+ * Get components filtered by category.
45
+ * @param {string} category — e.g. 'input', 'layout', 'display'
46
+ * @returns {object[]}
47
+ */
48
+ export async function getComponentsByGroup(category) {
49
+ return getComponentsByCategory(category);
50
+ }
51
+
52
+ /**
53
+ * Search the pattern library by query.
54
+ * @param {string} query — Natural language or keyword query
55
+ * @returns {object[]}
56
+ */
57
+ export function searchBlocks(query, { domain } = {}) {
58
+ return searchPatterns(query, { domain });
59
+ }
60
+
61
+ /**
62
+ * Get a specific named pattern.
63
+ * @param {string} name — Pattern name (e.g. 'stat-cards', 'user-profile')
64
+ * @returns {object|null}
65
+ */
66
+ export function lookupPattern(name) {
67
+ return getPattern(name);
68
+ }
69
+
70
+ /**
71
+ * Get all available patterns.
72
+ * @returns {object[]}
73
+ */
74
+ export function listPatterns() {
75
+ return getAllPatterns();
76
+ }
77
+
78
+ /**
79
+ * Semantic search — LLM-enhanced pattern matching.
80
+ * Falls back to keyword search if no adapter provided.
81
+ * @param {string} query
82
+ * @param {object} [options] — { llmAdapter, remix }
83
+ * @returns {Promise<{ matches: object[], remix?: object }>}
84
+ */
85
+ export async function searchBlocksSemantic(query, options = {}) {
86
+ return semanticSearchPatterns(query, options);
87
+ }
88
+
89
+ /**
90
+ * Classify an intent into a domain.
91
+ * @param {string} intent — Natural language intent
92
+ * @returns {object} — { domain, confidence, matchedSignals }
93
+ */
94
+ export function lookupDomain(intent) {
95
+ return classifyIntent(intent);
96
+ }
97
+
98
+ /**
99
+ * Assemble progressive-disclosure context for generation.
100
+ * @param {string} intent — Natural language intent
101
+ * @param {number} [tier=1] — Budget tier (0-4)
102
+ * @returns {object}
103
+ */
104
+ export async function getContext(intent, tier = 1) {
105
+ return assembleContext({ intent, tier });
106
+ }
107
+
108
+ /**
109
+ * Run anti-pattern checks against a component tree.
110
+ * @param {object[]} components — Flat component list
111
+ * @returns {object[]} — Anti-pattern violations
112
+ */
113
+ export function checkAntiPatterns(components) {
114
+ return checkAllAntiPatterns(components);
115
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared module-level singletons for the monolithic engine.
3
+ *
4
+ * These were previously private to engine/generator.js. Extracted so that
5
+ * engines/monolithic/generate-{instant,pro,thinking}.js can share the same
6
+ * artifact store and pipeline engine without going through the dispatcher.
7
+ *
8
+ * Same instances, imported by reference — do NOT re-instantiate per-call.
9
+ */
10
+
11
+ import { ArtifactStore } from './artifacts.js';
12
+ import { PipelineEngine } from './pipeline/engine.js';
13
+
14
+ export const store = new ArtifactStore();
15
+ export const engine = new PipelineEngine();