@praxisui/page-builder 8.0.0-beta.1 → 8.0.0-beta.12

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.
@@ -25,13 +25,13 @@ import * as i6 from '@angular/material/divider';
25
25
  import { MatDividerModule } from '@angular/material/divider';
26
26
  import * as i6$1 from '@angular/material/select';
27
27
  import { MatSelectModule } from '@angular/material/select';
28
- import { BehaviorSubject, merge, firstValueFrom } from 'rxjs';
28
+ import { BehaviorSubject, merge, switchMap, Observable, firstValueFrom, from, concatMap, catchError, of, lastValueFrom, tap } from 'rxjs';
29
29
  import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
30
30
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
31
31
  import * as i7 from '@angular/material/tabs';
32
32
  import { MatTabsModule } from '@angular/material/tabs';
33
33
  import { HttpClient, HttpHeaders } from '@angular/common/http';
34
- import { PraxisAssistantTurnOrchestratorService, PraxisAiAssistantShellComponent } from '@praxisui/ai';
34
+ import { AI_STREAM_EVENT_TYPES, PraxisAssistantTurnOrchestratorService, PraxisAiAssistantShellComponent } from '@praxisui/ai';
35
35
 
36
36
  const PLACEHOLDER = 1;
37
37
 
@@ -2345,6 +2345,7 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
2345
2345
  'praxis.pageBuilder.agentic.quickReplies.formSuggestion': 'Form',
2346
2346
  'praxis.pageBuilder.agentic.quickReplies.masterDetailSuggestion': 'Master detail',
2347
2347
  'praxis.pageBuilder.agentic.quickReplies.confirmPromptSuffix': 'Confirmed:',
2348
+ 'praxis.pageBuilder.agentic.quickReplies.resourceSelectionPrompt': 'Use {label} ({resourcePath}) as the data source.',
2348
2349
  'praxis.pageBuilder.agentic.attachments.currentPage': 'Current page',
2349
2350
  'praxis.pageBuilder.agentic.suggestionPrompts.payrollExecutiveDashboard': 'Create a payroll dashboard with KPIs, payroll by department, monthly evolution, and a detail table.',
2350
2351
  'praxis.pageBuilder.agentic.suggestionPrompts.payrollDepartmentDrilldown': 'Create a payroll dashboard with a department chart that filters a detail table when a bar is selected.',
@@ -2358,6 +2359,12 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
2358
2359
  'praxis.pageBuilder.agentic.exploratory.capability.table': 'tables',
2359
2360
  'praxis.pageBuilder.agentic.exploratory.capability.form': 'forms',
2360
2361
  'praxis.pageBuilder.agentic.exploratory.capability.masterDetail': 'master-detail pages',
2362
+ 'praxis.pageBuilder.agentic.resourceDiscovery.found': 'I found APIs that can feed this screen. Choose one before generating the preview.',
2363
+ 'praxis.pageBuilder.agentic.resourceDiscovery.empty': 'I did not find a matching API yet. Describe the business data this screen should use.',
2364
+ 'praxis.pageBuilder.agentic.resourceDiscovery.useResource': 'Use {resourcePath}',
2365
+ 'praxis.pageBuilder.agentic.diagnostics.title': 'LLM diagnostics',
2366
+ 'praxis.pageBuilder.agentic.diagnostics.badge': 'Debug',
2367
+ 'praxis.pageBuilder.agentic.diagnostics.description': 'Prompt, context bundle, and tool catalog returned by the backend for this turn.',
2361
2368
  'praxis.pageBuilder.agentic.dragHandleAria': 'Move AI assistant',
2362
2369
  'praxis.pageBuilder.agentic.resizeHandleAria': 'Resize AI assistant',
2363
2370
  'praxis.pageBuilder.agentic.resizeHandleTooltip': 'Resize',
@@ -2366,7 +2373,12 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
2366
2373
  'praxis.pageBuilder.agentic.status.resolvingIntent': 'Resolving intent...',
2367
2374
  'praxis.pageBuilder.agentic.status.waitingRevision': 'Refine your prompt and preview again.',
2368
2375
  'praxis.pageBuilder.agentic.status.cancelled': 'Request cancelled.',
2376
+ 'praxis.pageBuilder.agentic.status.contextBundle': 'Preparing context...',
2377
+ 'praxis.pageBuilder.agentic.status.resourceDiscovery': 'Finding API resources...',
2378
+ 'praxis.pageBuilder.agentic.status.resourceDiscoveryBackend': 'Found API resources in the backend catalog.',
2379
+ 'praxis.pageBuilder.agentic.status.refinedCandidates': 'Reviewing the retrieved resources with the AI...',
2369
2380
  'praxis.pageBuilder.agentic.status.previewing': 'Generating preview...',
2381
+ 'praxis.pageBuilder.agentic.status.previewCompile': 'Compiling preview...',
2370
2382
  'praxis.pageBuilder.agentic.status.previewReady': 'Preview applied to the page.',
2371
2383
  'praxis.pageBuilder.agentic.status.acceptedAddLocalField': 'Local field added to the form.',
2372
2384
  'praxis.pageBuilder.agentic.status.acceptedRemoveLocalField': 'Local field removed from the form.',
@@ -2377,8 +2389,12 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
2377
2389
  'praxis.pageBuilder.agentic.errors.componentId': 'Configure a component id before saving.',
2378
2390
  'praxis.pageBuilder.agentic.errors.invalidPreview': 'Generated preview is invalid.',
2379
2391
  'praxis.pageBuilder.agentic.errors.intentResolution': 'Intent could not be resolved.',
2392
+ 'praxis.pageBuilder.agentic.errors.streamTimeout': 'The assistant took too long to finish this request. Try again with a narrower request or review the active context.',
2393
+ 'praxis.pageBuilder.agentic.errors.streamProcessing': 'The assistant could not finish this authoring request. Try again or ask support to review the diagnostics.',
2394
+ 'praxis.pageBuilder.agentic.errors.streamConnection': 'The assistant stream was interrupted before the turn finished. Try again or ask support to check the stream connection.',
2380
2395
  'praxis.pageBuilder.agentic.errors.duplicateField': 'This field already exists in the selected form.',
2381
2396
  'praxis.pageBuilder.agentic.errors.nonLocalFieldRemoval': 'Only local fields created by AI can be removed in this flow.',
2397
+ 'praxis.pageBuilder.agentic.errors.invalidTableContract': 'I found the data source, but the generated plan used properties that are incompatible with the table component. I will adjust it to use only supported fields.',
2382
2398
  'praxis.pageBuilder.agentic.errors.generic': 'AI authoring failed.',
2383
2399
  };
2384
2400
 
@@ -2586,6 +2602,7 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
2586
2602
  'praxis.pageBuilder.agentic.quickReplies.formSuggestion': 'Formulário',
2587
2603
  'praxis.pageBuilder.agentic.quickReplies.masterDetailSuggestion': 'Master detail',
2588
2604
  'praxis.pageBuilder.agentic.quickReplies.confirmPromptSuffix': 'Confirmado:',
2605
+ 'praxis.pageBuilder.agentic.quickReplies.resourceSelectionPrompt': 'Usar {label} ({resourcePath}) como fonte de dados.',
2589
2606
  'praxis.pageBuilder.agentic.attachments.currentPage': 'Página atual',
2590
2607
  'praxis.pageBuilder.agentic.suggestionPrompts.payrollExecutiveDashboard': 'Crie um dashboard de folha de pagamento com KPIs, folha por departamento, evolução mensal e tabela de detalhes.',
2591
2608
  'praxis.pageBuilder.agentic.suggestionPrompts.payrollDepartmentDrilldown': 'Crie um dashboard de folha de pagamento com gráfico por departamento que filtre uma tabela detalhada ao selecionar uma barra.',
@@ -2599,6 +2616,12 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
2599
2616
  'praxis.pageBuilder.agentic.exploratory.capability.table': 'tabelas',
2600
2617
  'praxis.pageBuilder.agentic.exploratory.capability.form': 'formulários',
2601
2618
  'praxis.pageBuilder.agentic.exploratory.capability.masterDetail': 'páginas master-detail',
2619
+ 'praxis.pageBuilder.agentic.resourceDiscovery.found': 'Encontrei APIs que podem alimentar esta tela. Escolha uma antes de gerar a pré-visualização.',
2620
+ 'praxis.pageBuilder.agentic.resourceDiscovery.empty': 'Ainda não encontrei uma API correspondente. Descreva quais dados de negócio esta tela deve usar.',
2621
+ 'praxis.pageBuilder.agentic.resourceDiscovery.useResource': 'Usar {resourcePath}',
2622
+ 'praxis.pageBuilder.agentic.diagnostics.title': 'Diagnóstico do LLM',
2623
+ 'praxis.pageBuilder.agentic.diagnostics.badge': 'Debug',
2624
+ 'praxis.pageBuilder.agentic.diagnostics.description': 'Prompt, pacote de contexto e catálogo de ferramentas retornados pelo backend para este turno.',
2602
2625
  'praxis.pageBuilder.agentic.dragHandleAria': 'Mover assistente de IA',
2603
2626
  'praxis.pageBuilder.agentic.resizeHandleAria': 'Redimensionar assistente de IA',
2604
2627
  'praxis.pageBuilder.agentic.resizeHandleTooltip': 'Redimensionar',
@@ -2607,7 +2630,12 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
2607
2630
  'praxis.pageBuilder.agentic.status.resolvingIntent': 'Resolvendo intenção...',
2608
2631
  'praxis.pageBuilder.agentic.status.waitingRevision': 'Ajuste o prompt e pré-visualize novamente.',
2609
2632
  'praxis.pageBuilder.agentic.status.cancelled': 'Solicitação cancelada.',
2633
+ 'praxis.pageBuilder.agentic.status.contextBundle': 'Preparando contexto...',
2634
+ 'praxis.pageBuilder.agentic.status.resourceDiscovery': 'Buscando recursos de API...',
2635
+ 'praxis.pageBuilder.agentic.status.resourceDiscoveryBackend': 'Encontrei recursos de API no catálogo do backend.',
2636
+ 'praxis.pageBuilder.agentic.status.refinedCandidates': 'Estou revisando os recursos encontrados com a IA...',
2610
2637
  'praxis.pageBuilder.agentic.status.previewing': 'Gerando pré-visualização...',
2638
+ 'praxis.pageBuilder.agentic.status.previewCompile': 'Compilando pré-visualização...',
2611
2639
  'praxis.pageBuilder.agentic.status.previewReady': 'Pré-visualização aplicada à página.',
2612
2640
  'praxis.pageBuilder.agentic.status.acceptedAddLocalField': 'Campo local adicionado ao formulário.',
2613
2641
  'praxis.pageBuilder.agentic.status.acceptedRemoveLocalField': 'Campo local removido do formulário.',
@@ -2618,8 +2646,12 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
2618
2646
  'praxis.pageBuilder.agentic.errors.componentId': 'Configure um id de componente antes de salvar.',
2619
2647
  'praxis.pageBuilder.agentic.errors.invalidPreview': 'A pré-visualização gerada é inválida.',
2620
2648
  'praxis.pageBuilder.agentic.errors.intentResolution': 'A intenção não pôde ser resolvida.',
2649
+ 'praxis.pageBuilder.agentic.errors.streamTimeout': 'Ainda não consegui concluir este pedido. Tente novamente com um pedido mais específico ou revise o contexto ativo.',
2650
+ 'praxis.pageBuilder.agentic.errors.streamProcessing': 'Não consegui concluir este pedido de autoria. Tente novamente ou peça ao suporte para revisar o diagnóstico.',
2651
+ 'praxis.pageBuilder.agentic.errors.streamConnection': 'O stream do assistente foi interrompido antes de concluir o turno. Tente novamente ou acione o suporte para verificar a conexão do stream.',
2621
2652
  'praxis.pageBuilder.agentic.errors.duplicateField': 'Este campo já existe no formulário selecionado.',
2622
2653
  'praxis.pageBuilder.agentic.errors.nonLocalFieldRemoval': 'Somente campos locais criados pela IA podem ser removidos neste fluxo.',
2654
+ 'praxis.pageBuilder.agentic.errors.invalidTableContract': 'Encontrei a fonte de dados, mas o plano gerado usou propriedades incompatíveis com o componente de tabela. Vou ajustar para usar apenas os campos suportados.',
2623
2655
  'praxis.pageBuilder.agentic.errors.generic': 'Falha na autoria com IA.',
2624
2656
  };
2625
2657
 
@@ -4778,6 +4810,9 @@ const PAGE_BUILDER_AI_CAPABILITIES = {
4778
4810
  'Taxonomia editorial: condition usa Json Logic canonico; transform usa pipeline declarativo; policy cobre apenas comportamento operacional.',
4779
4811
  'Para criacao agentic de paginas, o preview pode trazer uiCompositionPlan como plano intermediario validavel; o page-builder compila esse plano para WidgetPageDefinition antes de aplicar.',
4780
4812
  'UiCompositionPlan usa widgets[].componentId e bindings declarativos. O JSON persistido continua sendo page.widgets[].definition.id e page.composition.links.',
4813
+ 'Nested component ports usam endpoint component-port com nestedPath. O owner em ref.widget, bindings[].from.widget ou bindings[].to.widget continua sendo o widget top-level; a porta real do filho fica em port/direction e o caminho ate o filho fica em nestedPath.',
4814
+ 'Nested component ports usam endpoint component-port com nestedPath. O owner em ref.widget ou bindings[].*.widget continua sendo o widget top-level; a porta real do filho fica em port/direction e o caminho ate o filho fica em nestedPath.',
4815
+ 'Nao gere links nested via owner.widgetEvent, bindingPath profundo, page.connections ou wrappers host-specific. widgetEvent e bridge legada/avancada, nao contrato principal de authoring.',
4781
4816
  'Payloads legados de layout ainda podem ser ingeridos, mas sao normalizados para canvas e nao devem ser reemitidos.',
4782
4817
  'pageIdentity identifica o escopo de persistencia e nao pertence ao objeto page.',
4783
4818
  ],
@@ -4796,6 +4831,18 @@ const PAGE_BUILDER_AI_CAPABILITIES = {
4796
4831
  { path: 'uiCompositionPlan.bindings[].id', category: 'connections', valueKind: 'string', description: 'Identificador estavel do binding planejado.' },
4797
4832
  { path: 'uiCompositionPlan.bindings[].from', category: 'connections', valueKind: 'object', description: 'Endpoint de origem: component-port ou state.' },
4798
4833
  { path: 'uiCompositionPlan.bindings[].to', category: 'connections', valueKind: 'object', description: 'Endpoint de destino: component-port ou state.' },
4834
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath', category: 'connections', valueKind: 'array', description: 'Caminho estruturado para porta nested de origem. Use apenas com endpoint component-port; o owner top-level continua em from.widget.' },
4835
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath', category: 'connections', valueKind: 'array', description: 'Caminho estruturado para porta nested de destino. Use apenas com endpoint component-port; o owner top-level continua em to.widget.' },
4836
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath[].kind', category: 'connections', valueKind: 'string', description: 'Tipo do segmento nested de origem, como tab, nav, link, expansion, panel ou widget. O ultimo segmento deve ser widget.' },
4837
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath[].id', category: 'connections', valueKind: 'string', description: 'Identificador estrutural estavel do container de origem no segmento nested, quando existir.' },
4838
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath[].key', category: 'connections', valueKind: 'string', description: 'Chave estavel do widget filho no segmento terminal widget de origem. Obrigatoria para endpoint nested persistido.', critical: true },
4839
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath[].index', category: 'connections', valueKind: 'number', description: 'Indice auxiliar de origem para contexto visual/diagnostico; nao use como identidade primaria persistida.' },
4840
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath[].componentType', category: 'connections', valueKind: 'string', description: 'Tipo do componente real do widget filho de origem, usado como verificacao auxiliar, nao como identidade primaria.' },
4841
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath[].kind', category: 'connections', valueKind: 'string', description: 'Tipo do segmento nested de destino, como tab, nav, link, expansion, panel ou widget. O ultimo segmento deve ser widget.' },
4842
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath[].id', category: 'connections', valueKind: 'string', description: 'Identificador estrutural estavel do container de destino no segmento nested, quando existir.' },
4843
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath[].key', category: 'connections', valueKind: 'string', description: 'Chave estavel do widget filho no segmento terminal widget de destino. Obrigatoria para endpoint nested persistido.', critical: true },
4844
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath[].index', category: 'connections', valueKind: 'number', description: 'Indice auxiliar de destino para contexto visual/diagnostico; nao use como identidade primaria persistida.' },
4845
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath[].componentType', category: 'connections', valueKind: 'string', description: 'Tipo do componente real do widget filho de destino, usado como verificacao auxiliar, nao como identidade primaria.' },
4799
4846
  { path: 'uiCompositionPlan.bindings[].intent', category: 'connections', valueKind: 'string', description: 'Intencao semantica do binding.' },
4800
4847
  { path: 'uiCompositionPlan.bindings[].condition', category: 'connections', valueKind: 'expression', description: 'Guarda opcional em Json Logic.' },
4801
4848
  { path: 'uiCompositionPlan.bindings[].transform', category: 'connections', valueKind: 'object', description: 'Transform declarativo: pick-path, query-context, template ou constant.' },
@@ -4847,6 +4894,10 @@ const PAGE_BUILDER_AI_CAPABILITIES = {
4847
4894
  { path: 'page.composition.links[].id', category: 'connections', valueKind: 'string', description: 'Identificador estavel do link.' },
4848
4895
  { path: 'page.composition.links[].from', category: 'connections', valueKind: 'object', description: 'Endpoint de origem do link.' },
4849
4896
  { path: 'page.composition.links[].to', category: 'connections', valueKind: 'object', description: 'Endpoint de destino do link.' },
4897
+ { path: 'page.composition.links[].from.ref.nestedPath', category: 'connections', valueKind: 'array', description: 'NestedPath canonico em endpoint component-port de origem persistido. Omita para top-level; nao serialize nestedPath: [].' },
4898
+ { path: 'page.composition.links[].from.ref.nestedPath[].key', category: 'connections', valueKind: 'string', description: 'Chave estavel do widget filho no segmento terminal widget de origem. Obrigatoria para endpoint nested persistido.', critical: true },
4899
+ { path: 'page.composition.links[].to.ref.nestedPath', category: 'connections', valueKind: 'array', description: 'NestedPath canonico em endpoint component-port de destino persistido. Omita para top-level; nao serialize nestedPath: [].' },
4900
+ { path: 'page.composition.links[].to.ref.nestedPath[].key', category: 'connections', valueKind: 'string', description: 'Chave estavel do widget filho no segmento terminal widget de destino. Obrigatoria para endpoint nested persistido.', critical: true },
4850
4901
  { path: 'page.composition.links[].intent', category: 'connections', valueKind: 'string', description: 'Intencao semantica do link.' },
4851
4902
  { path: 'page.composition.links[].transform', category: 'connections', valueKind: 'object', description: 'Pipeline de transformacao do link.' },
4852
4903
  { path: 'page.composition.links[].condition', category: 'connections', valueKind: 'expression', description: 'Guarda semantica opcional do link, expressa como um unico AST Json Logic canonico.' },
@@ -5026,6 +5077,12 @@ class PageBuilderAgenticAuthoringService {
5026
5077
  resolveIntent(request) {
5027
5078
  return this.http.post(`${this.baseUrl}/intent-resolution`, request, { headers: this.buildHeaders() });
5028
5079
  }
5080
+ searchResourceCandidates(request) {
5081
+ return this.http.post(`${this.baseUrl}/resource-candidates`, request, { headers: this.buildHeaders() });
5082
+ }
5083
+ streamTurn(request) {
5084
+ return this.http.post(`${this.baseUrl}/turn/stream/start`, request, { headers: this.buildHeaders() }).pipe(switchMap((start) => this.connectTurnStream(start)));
5085
+ }
5029
5086
  applyPage(request) {
5030
5087
  const { ifMatch, ...body } = request;
5031
5088
  return this.http.post(`${this.baseUrl}/page-apply`, {
@@ -5055,6 +5112,97 @@ class PageBuilderAgenticAuthoringService {
5055
5112
  }
5056
5113
  return new HttpHeaders(merged);
5057
5114
  }
5115
+ connectTurnStream(start) {
5116
+ return new Observable((observer) => {
5117
+ const url = this.buildStreamUrl(start);
5118
+ let source;
5119
+ let closed = false;
5120
+ let connectionErrorTimer = null;
5121
+ const connectionErrorGraceMs = Math.max(0, this.options?.streamConnectionErrorGraceMs ?? 1500);
5122
+ try {
5123
+ source = this.createEventSource(url);
5124
+ }
5125
+ catch (error) {
5126
+ observer.error(this.streamConnectionError(error));
5127
+ return undefined;
5128
+ }
5129
+ const clearConnectionErrorTimer = () => {
5130
+ if (connectionErrorTimer) {
5131
+ clearTimeout(connectionErrorTimer);
5132
+ connectionErrorTimer = null;
5133
+ }
5134
+ };
5135
+ const closeSource = () => {
5136
+ if (closed) {
5137
+ return;
5138
+ }
5139
+ closed = true;
5140
+ clearConnectionErrorTimer();
5141
+ source.close();
5142
+ };
5143
+ const failConnection = (event) => {
5144
+ if (closed) {
5145
+ return;
5146
+ }
5147
+ observer.error(this.streamConnectionError(event));
5148
+ closeSource();
5149
+ };
5150
+ const handleMessage = (event) => {
5151
+ try {
5152
+ clearConnectionErrorTimer();
5153
+ const parsed = JSON.parse(event.data);
5154
+ observer.next(parsed);
5155
+ if (parsed.type === 'result' || parsed.type === 'error' || parsed.type === 'cancelled') {
5156
+ observer.complete();
5157
+ closeSource();
5158
+ }
5159
+ }
5160
+ catch (error) {
5161
+ observer.error(this.streamConnectionError(error));
5162
+ closeSource();
5163
+ }
5164
+ };
5165
+ source.onmessage = handleMessage;
5166
+ if (source.addEventListener) {
5167
+ for (const type of AI_STREAM_EVENT_TYPES) {
5168
+ source.addEventListener(type, handleMessage);
5169
+ }
5170
+ }
5171
+ source.onerror = (event) => {
5172
+ if (closed || connectionErrorTimer) {
5173
+ return;
5174
+ }
5175
+ if (connectionErrorGraceMs === 0) {
5176
+ failConnection(event);
5177
+ return;
5178
+ }
5179
+ connectionErrorTimer = setTimeout(() => failConnection(event), connectionErrorGraceMs);
5180
+ };
5181
+ return () => closeSource();
5182
+ });
5183
+ }
5184
+ streamConnectionError(cause) {
5185
+ return {
5186
+ praxisAgenticTurnStreamConnectionError: true,
5187
+ cause,
5188
+ };
5189
+ }
5190
+ buildStreamUrl(start) {
5191
+ const url = new URL(`${this.baseUrl}/turn/stream/${start.streamId}`, typeof window !== 'undefined' ? window.location.origin : 'http://localhost');
5192
+ if (start.streamAccessToken) {
5193
+ url.searchParams.set('accessToken', start.streamAccessToken);
5194
+ }
5195
+ return /^https?:\/\//i.test(this.baseUrl)
5196
+ ? url.toString()
5197
+ : url.pathname + url.search;
5198
+ }
5199
+ createEventSource(url) {
5200
+ const factory = this.options?.eventSourceFactory;
5201
+ if (factory) {
5202
+ return factory(url);
5203
+ }
5204
+ return new EventSource(url);
5205
+ }
5058
5206
  formatEtag(etag) {
5059
5207
  const trimmed = etag.trim();
5060
5208
  return trimmed.startsWith('"') ? trimmed : `"${trimmed}"`;
@@ -5067,6 +5215,522 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
5067
5215
  args: [{ providedIn: 'root' }]
5068
5216
  }] });
5069
5217
 
5218
+ const pageSchema = {
5219
+ type: 'object',
5220
+ properties: {
5221
+ title: { type: 'string' },
5222
+ layoutPreset: { type: 'string' },
5223
+ context: { type: 'object' },
5224
+ metadata: { type: 'object' },
5225
+ },
5226
+ };
5227
+ const canvasSchema = {
5228
+ type: 'object',
5229
+ properties: {
5230
+ columns: { type: 'number' },
5231
+ rowUnit: { type: 'number' },
5232
+ gap: { type: 'number' },
5233
+ items: { type: 'array' },
5234
+ },
5235
+ };
5236
+ const widgetSchema = {
5237
+ type: 'object',
5238
+ required: ['widgetKey', 'componentId'],
5239
+ properties: {
5240
+ widgetKey: { type: 'string' },
5241
+ componentId: { type: 'string' },
5242
+ title: { type: 'string' },
5243
+ inputs: { type: 'object' },
5244
+ canvasItem: { type: 'object' },
5245
+ },
5246
+ };
5247
+ const compositionLinkSchema = {
5248
+ type: 'object',
5249
+ required: ['linkId', 'from', 'to'],
5250
+ properties: {
5251
+ linkId: { type: 'string' },
5252
+ from: { type: 'object' },
5253
+ to: { type: 'object' },
5254
+ condition: { type: 'object' },
5255
+ policy: { type: 'object' },
5256
+ },
5257
+ };
5258
+ const childOperationSchema = {
5259
+ type: 'object',
5260
+ required: ['widgetKey', 'childComponentId', 'childOperationId', 'reason'],
5261
+ properties: {
5262
+ widgetKey: { type: 'string' },
5263
+ childComponentId: { type: 'string' },
5264
+ childOperationId: { type: 'string' },
5265
+ reason: { type: 'string' },
5266
+ childTarget: { type: 'object' },
5267
+ childParams: { type: 'object' },
5268
+ },
5269
+ };
5270
+ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
5271
+ schemaVersion: '1.0.0',
5272
+ componentId: 'praxis-page-builder',
5273
+ ownerPackage: '@praxisui/page-builder',
5274
+ configSchemaId: 'WidgetPageDefinition',
5275
+ manifestVersion: '1.0.0',
5276
+ runtimeInputs: [
5277
+ { name: 'page', type: 'WidgetPageDefinition', description: 'Canonical renderable page document consumed by praxis-dynamic-page.' },
5278
+ { name: 'pageIdentity', type: 'PageIdentity | null', description: 'Persistence identity for ui_user_config save/apply flows; not persisted inside page.' },
5279
+ { name: 'enableCustomization', type: 'boolean', description: 'Enables page-builder authoring chrome around the runtime page.' },
5280
+ { name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context passed to catalog resolution and agentic authoring.' },
5281
+ { name: 'pageChange', type: 'WidgetPageDefinition', description: 'Emitted after local apply/preview produces a renderable page.' },
5282
+ { name: 'agenticAuthoringEnableStreaming', type: 'boolean', description: 'Opt-in flag for the canonical streaming turn endpoints.' },
5283
+ ],
5284
+ editableTargets: [
5285
+ { kind: 'page', resolver: 'widget-page-definition-root', description: 'Root WidgetPageDefinition document: title, metadata, context and renderable page envelope.' },
5286
+ { kind: 'canvas', resolver: 'widget-page-canvas', description: 'Canvas geometry and item placement for top-level widgets.' },
5287
+ { kind: 'widget', resolver: 'widget-by-stable-key', description: 'Widget definitions addressed by stable widgetKey, never by array index.' },
5288
+ { kind: 'widgetShell', resolver: 'widget-shell-by-widget-key', description: 'Dashboard-card shell metadata such as title, action chrome and visual preset.' },
5289
+ { kind: 'compositionLink', resolver: 'composition-link-by-id', description: 'Declarative wiring in page.composition.links, including component-port nestedPath endpoints.' },
5290
+ { kind: 'state', resolver: 'page-state-path', description: 'Page-level state entries referenced by composition links.' },
5291
+ { kind: 'pageIdentity', resolver: 'page-builder-persistence-identity', description: 'ui_user_config identity, scope and ETag used by applyPage/save.' },
5292
+ { kind: 'agenticPreview', resolver: 'agentic-preview-result', description: 'Compiled backend preview envelope whose patch.page is applied locally.' },
5293
+ { kind: 'childOperation', resolver: 'widget-child-authoring-manifest-operation', description: 'Delegated operation owned by the child widget component manifest or configEditor.' },
5294
+ ],
5295
+ operations: [
5296
+ {
5297
+ operationId: 'page.configure',
5298
+ title: 'Configure page metadata',
5299
+ scope: 'global',
5300
+ targetKind: 'page',
5301
+ target: { kind: 'page', resolver: 'widget-page-definition-root', ambiguityPolicy: 'fail', required: false },
5302
+ inputSchema: pageSchema,
5303
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-page-configure', handlerContract: {
5304
+ reads: ['WidgetPageDefinition', 'DynamicPageConfigEditorComponent', 'SettingsPanelBridge'],
5305
+ writes: ['page.title', 'page.layoutPreset', 'page.context', 'page.metadata'],
5306
+ identityKeys: ['pageIdentity.componentId', 'pageIdentity.scope'],
5307
+ inputSchema: pageSchema,
5308
+ failureModes: ['page-not-loaded', 'context-not-json', 'settings-panel-bridge-missing'],
5309
+ description: 'Updates root page metadata while preserving the renderable WidgetPageDefinition shape.',
5310
+ } }],
5311
+ validators: ['widget-page-definition-valid', 'no-legacy-grid-page-definition', 'page-context-json-valid', 'settings-panel-round-trip-valid'],
5312
+ affectedPaths: ['title', 'layoutPreset', 'context', 'metadata'],
5313
+ submissionImpact: false,
5314
+ preconditions: ['page-loaded'],
5315
+ },
5316
+ {
5317
+ operationId: 'canvas.configure',
5318
+ title: 'Configure page canvas',
5319
+ scope: 'layout',
5320
+ targetKind: 'canvas',
5321
+ target: { kind: 'canvas', resolver: 'widget-page-canvas', ambiguityPolicy: 'fail', required: true },
5322
+ inputSchema: canvasSchema,
5323
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-canvas-configure', handlerContract: {
5324
+ reads: ['WidgetPageDefinition.canvas', 'DynamicPageBuilderComponent'],
5325
+ writes: ['page.canvas.columns', 'page.canvas.rowUnit', 'page.canvas.gap', 'page.canvas.items'],
5326
+ identityKeys: ['canvas'],
5327
+ inputSchema: canvasSchema,
5328
+ failureModes: ['canvas-missing', 'canvas-item-references-missing-widget', 'invalid-grid-geometry'],
5329
+ description: 'Configures the top-level canvas without introducing legacy GridPageDefinition semantics.',
5330
+ } }],
5331
+ validators: ['canvas-columns-integer', 'canvas-row-unit-valid', 'canvas-gap-valid', 'canvas-items-reference-existing-widgets'],
5332
+ affectedPaths: ['canvas.columns', 'canvas.rowUnit', 'canvas.gap', 'canvas.items'],
5333
+ submissionImpact: false,
5334
+ preconditions: ['page-loaded'],
5335
+ },
5336
+ {
5337
+ operationId: 'widget.add',
5338
+ title: 'Add widget to page',
5339
+ scope: 'layout',
5340
+ targetKind: 'widget',
5341
+ target: { kind: 'widget', resolver: 'widget-by-stable-key', ambiguityPolicy: 'fail', required: true },
5342
+ inputSchema: widgetSchema,
5343
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-widget-add', handlerContract: {
5344
+ reads: ['WidgetPageDefinition.widgets', 'WidgetPageDefinition.canvas', 'ComponentDocRegistry', 'PAGE_BUILDER_WIDGET_AI_CATALOGS'],
5345
+ writes: ['page.widgets[]', 'page.canvas.items[]'],
5346
+ identityKeys: ['widgetKey'],
5347
+ inputSchema: widgetSchema,
5348
+ failureModes: ['widget-key-duplicate', 'component-not-registered', 'canvas-item-invalid', 'child-input-contract-unknown'],
5349
+ description: 'Adds a registered component as a widget and places it on the canvas using a stable widgetKey.',
5350
+ } }],
5351
+ validators: ['widget-key-unique', 'component-registered', 'canvas-item-valid', 'child-inputs-delegated', 'widget-key-not-array-index'],
5352
+ affectedPaths: ['widgets[]', 'canvas.items[]'],
5353
+ submissionImpact: false,
5354
+ preconditions: ['page-loaded', 'component-catalog-loaded'],
5355
+ },
5356
+ {
5357
+ operationId: 'widget.remove',
5358
+ title: 'Remove widget from page',
5359
+ scope: 'layout',
5360
+ targetKind: 'widget',
5361
+ target: { kind: 'widget', resolver: 'widget-by-stable-key', ambiguityPolicy: 'fail', required: true },
5362
+ inputSchema: { type: 'object', required: ['widgetKey'], properties: { widgetKey: { type: 'string' }, confirmed: { type: 'boolean' } } },
5363
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-widget-remove', handlerContract: {
5364
+ reads: ['WidgetPageDefinition.widgets', 'WidgetPageDefinition.canvas.items', 'WidgetPageDefinition.composition.links'],
5365
+ writes: ['page.widgets[]', 'page.canvas.items[]', 'page.composition.links[]'],
5366
+ identityKeys: ['widgetKey'],
5367
+ failureModes: ['widget-not-found', 'destructive-removal-not-confirmed', 'dangling-composition-link'],
5368
+ description: 'Removes a widget and cleans canvas items and composition links that reference it.',
5369
+ } }],
5370
+ destructive: true,
5371
+ requiresConfirmation: true,
5372
+ validators: ['widget-exists', 'destructive-removal-confirmed', 'composition-links-cleaned', 'canvas-items-reference-existing-widgets'],
5373
+ affectedPaths: ['widgets[]', 'canvas.items[]', 'composition.links[]'],
5374
+ submissionImpact: false,
5375
+ preconditions: ['page-loaded', 'target-widget-exists'],
5376
+ },
5377
+ {
5378
+ operationId: 'widget.moveResize',
5379
+ title: 'Move or resize widget',
5380
+ scope: 'layout',
5381
+ targetKind: 'widget',
5382
+ target: { kind: 'widget', resolver: 'widget-by-stable-key', ambiguityPolicy: 'fail', required: true },
5383
+ inputSchema: { type: 'object', required: ['widgetKey', 'canvasItem'], properties: { widgetKey: { type: 'string' }, canvasItem: { type: 'object' } } },
5384
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-widget-move-resize', handlerContract: {
5385
+ reads: ['WidgetPageDefinition.canvas.items', 'WidgetPageDefinition.widgets'],
5386
+ writes: ['page.canvas.items[]'],
5387
+ identityKeys: ['widgetKey'],
5388
+ failureModes: ['widget-not-found', 'canvas-item-invalid', 'canvas-overlap-invalid'],
5389
+ description: 'Updates canvas placement for an existing widget using widgetKey as the stable identity.',
5390
+ } }],
5391
+ validators: ['widget-exists', 'canvas-item-valid', 'canvas-item-targets-existing-widget', 'canvas-overlap-policy-valid'],
5392
+ affectedPaths: ['canvas.items[]'],
5393
+ submissionImpact: false,
5394
+ preconditions: ['page-loaded', 'target-widget-exists'],
5395
+ },
5396
+ {
5397
+ operationId: 'widget.shell.configure',
5398
+ title: 'Configure widget shell',
5399
+ scope: 'templating',
5400
+ targetKind: 'widgetShell',
5401
+ target: { kind: 'widgetShell', resolver: 'widget-shell-by-widget-key', ambiguityPolicy: 'fail', required: true },
5402
+ inputSchema: { type: 'object', required: ['widgetKey'], properties: { widgetKey: { type: 'string' }, shell: { type: 'object' } } },
5403
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-widget-shell-configure', handlerContract: {
5404
+ reads: ['WidgetPageDefinition.widgets', 'WidgetShellEditorComponent', 'SettingsPanelBridge'],
5405
+ writes: ['page.widgets[].shell'],
5406
+ identityKeys: ['widgetKey'],
5407
+ failureModes: ['widget-not-found', 'settings-panel-bridge-missing', 'shell-config-invalid'],
5408
+ description: 'Updates the page-builder-owned widget shell without editing child component inputs.',
5409
+ } }],
5410
+ validators: ['widget-exists', 'widget-shell-shape-valid', 'settings-panel-bridge-available', 'child-inputs-not-mutated'],
5411
+ affectedPaths: ['widgets[].shell'],
5412
+ submissionImpact: false,
5413
+ preconditions: ['page-loaded', 'target-widget-exists'],
5414
+ },
5415
+ {
5416
+ operationId: 'composition.link.add',
5417
+ title: 'Add page composition link',
5418
+ scope: 'dataBinding',
5419
+ targetKind: 'compositionLink',
5420
+ target: { kind: 'compositionLink', resolver: 'composition-link-by-id', ambiguityPolicy: 'fail', required: true },
5421
+ inputSchema: compositionLinkSchema,
5422
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-composition-link-add', handlerContract: {
5423
+ reads: ['WidgetPageDefinition.composition.links', 'WidgetPageDefinition.widgets', 'PAGE_BUILDER_AI_CAPABILITIES'],
5424
+ writes: ['page.composition.links[]'],
5425
+ identityKeys: ['linkId'],
5426
+ inputSchema: compositionLinkSchema,
5427
+ failureModes: ['link-id-duplicate', 'endpoint-not-found', 'nested-path-invalid', 'json-logic-invalid'],
5428
+ description: 'Adds canonical declarative wiring in page.composition.links, including component-port nestedPath endpoints.',
5429
+ } }],
5430
+ validators: ['composition-link-id-unique', 'composition-endpoints-resolve', 'nested-path-terminal-widget-key-required', 'json-logic-condition-valid', 'no-legacy-connections-write'],
5431
+ affectedPaths: ['composition.links[]'],
5432
+ submissionImpact: false,
5433
+ preconditions: ['page-loaded', 'widgets-resolvable'],
5434
+ },
5435
+ {
5436
+ operationId: 'composition.link.remove',
5437
+ title: 'Remove page composition link',
5438
+ scope: 'dataBinding',
5439
+ targetKind: 'compositionLink',
5440
+ target: { kind: 'compositionLink', resolver: 'composition-link-by-id', ambiguityPolicy: 'fail', required: true },
5441
+ inputSchema: { type: 'object', required: ['linkId'], properties: { linkId: { type: 'string' }, confirmed: { type: 'boolean' } } },
5442
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-composition-link-remove', handlerContract: {
5443
+ reads: ['WidgetPageDefinition.composition.links'],
5444
+ writes: ['page.composition.links[]'],
5445
+ identityKeys: ['linkId'],
5446
+ failureModes: ['composition-link-not-found', 'destructive-removal-not-confirmed'],
5447
+ description: 'Removes a canonical composition link by stable linkId.',
5448
+ } }],
5449
+ destructive: true,
5450
+ requiresConfirmation: true,
5451
+ validators: ['composition-link-exists', 'destructive-removal-confirmed', 'composition-links-still-valid'],
5452
+ affectedPaths: ['composition.links[]'],
5453
+ submissionImpact: false,
5454
+ preconditions: ['page-loaded', 'target-link-exists'],
5455
+ },
5456
+ {
5457
+ operationId: 'composition.plan.compile',
5458
+ title: 'Compile UI composition plan',
5459
+ scope: 'rules',
5460
+ targetKind: 'agenticPreview',
5461
+ target: { kind: 'agenticPreview', resolver: 'agentic-preview-result', ambiguityPolicy: 'fail', required: true },
5462
+ inputSchema: { type: 'object', required: ['uiCompositionPlan'], properties: { uiCompositionPlan: { type: 'object' } } },
5463
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-ui-composition-plan-compile', handlerContract: {
5464
+ reads: ['UiCompositionPlan', 'compileUiCompositionPlan', 'PAGE_BUILDER_WIDGET_AI_CATALOGS', 'ComponentDocMeta.configEditor'],
5465
+ writes: ['compiledFormPatch.patch.page'],
5466
+ identityKeys: ['uiCompositionPlan.id', 'widgetKey', 'linkId'],
5467
+ failureModes: ['ui-composition-plan-invalid', 'widget-catalog-missing', 'compiled-page-invalid', 'child-config-editor-missing'],
5468
+ description: 'Compiles the agentic intermediate UiCompositionPlan into a renderable WidgetPageDefinition patch.page.',
5469
+ } }],
5470
+ validators: ['ui-composition-plan-valid', 'compiled-page-valid', 'widget-keys-unique', 'composition-endpoints-resolve', 'canvas-items-reference-existing-widgets'],
5471
+ affectedPaths: ['uiCompositionPlan', 'compiledFormPatch.patch.page'],
5472
+ submissionImpact: false,
5473
+ preconditions: ['agentic-preview-loaded', 'component-catalog-loaded'],
5474
+ },
5475
+ {
5476
+ operationId: 'state.set',
5477
+ title: 'Set page state value',
5478
+ scope: 'dataBinding',
5479
+ targetKind: 'state',
5480
+ target: { kind: 'state', resolver: 'page-state-path', ambiguityPolicy: 'fail', required: true },
5481
+ inputSchema: { type: 'object', required: ['path', 'value'], properties: { path: { type: 'string' }, value: {}, layer: { enum: ['page', 'session'] } } },
5482
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-state-set', handlerContract: {
5483
+ reads: ['WidgetPageDefinition.state', 'WidgetPageDefinition.composition.links'],
5484
+ writes: ['page.state'],
5485
+ identityKeys: ['path'],
5486
+ failureModes: ['state-path-invalid', 'state-json-invalid', 'composition-state-link-invalid'],
5487
+ description: 'Sets canonical page state used by composition links without creating host-only binding paths.',
5488
+ } }],
5489
+ validators: ['state-path-valid', 'state-layer-valid', 'state-json-valid', 'composition-state-links-still-valid'],
5490
+ affectedPaths: ['state'],
5491
+ submissionImpact: false,
5492
+ preconditions: ['page-loaded'],
5493
+ },
5494
+ {
5495
+ operationId: 'page.preview.apply',
5496
+ title: 'Apply agentic preview locally',
5497
+ scope: 'interaction',
5498
+ targetKind: 'agenticPreview',
5499
+ target: { kind: 'agenticPreview', resolver: 'agentic-preview-result', ambiguityPolicy: 'fail', required: true },
5500
+ inputSchema: { type: 'object', required: ['previewId'], properties: { previewId: { type: 'string' }, compiledFormPatch: { type: 'object' } } },
5501
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-preview-apply', handlerContract: {
5502
+ reads: ['PageBuilderAiAdapter.applyCompiledFormPatch', 'compiledFormPatch.patch.page', 'PraxisAssistantTurnViewState'],
5503
+ writes: ['DynamicPageBuilderComponent.page', 'pageChange'],
5504
+ identityKeys: ['previewId'],
5505
+ failureModes: ['preview-result-invalid', 'patch-page-missing', 'runtime-page-invalid', 'patch-envelope-persistence-attempted'],
5506
+ description: 'Applies only compiledFormPatch.patch.page to the local runtime page; the backend envelope remains diagnostic data.',
5507
+ } }],
5508
+ validators: ['preview-result-valid', 'compiled-page-patch-present', 'no-envelope-runtime-persistence', 'runtime-page-valid'],
5509
+ affectedPaths: ['compiledFormPatch.patch.page', 'page'],
5510
+ submissionImpact: false,
5511
+ preconditions: ['agentic-preview-loaded'],
5512
+ },
5513
+ {
5514
+ operationId: 'page.persist.save',
5515
+ title: 'Persist page through config API',
5516
+ scope: 'interaction',
5517
+ targetKind: 'pageIdentity',
5518
+ target: { kind: 'pageIdentity', resolver: 'page-builder-persistence-identity', ambiguityPolicy: 'fail', required: true },
5519
+ inputSchema: { type: 'object', required: ['pageIdentity'], properties: { pageIdentity: { type: 'object' }, page: { type: 'object' }, etag: { type: 'string' } } },
5520
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-page-persist-save', handlerContract: {
5521
+ reads: ['pageIdentity', 'WidgetPageDefinition', 'PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS', 'ui_user_config ETag'],
5522
+ writes: ['POST/PUT /api/praxis/config/ui-user-config', 'If-Match'],
5523
+ identityKeys: ['pageIdentity.componentType', 'pageIdentity.componentId', 'pageIdentity.scope'],
5524
+ failureModes: ['page-identity-incomplete', 'etag-conflict', 'runtime-page-invalid', 'patch-envelope-persistence-attempted'],
5525
+ description: 'Persists the renderable WidgetPageDefinition with pageIdentity and ETag delegated to praxis-config-starter.',
5526
+ } }],
5527
+ validators: ['page-identity-complete', 'etag-policy-valid', 'runtime-page-valid', 'no-envelope-runtime-persistence'],
5528
+ affectedPaths: ['page', 'pageIdentity', 'etag'],
5529
+ submissionImpact: false,
5530
+ preconditions: ['page-loaded', 'page-identity-known'],
5531
+ },
5532
+ {
5533
+ operationId: 'childOperation.delegate',
5534
+ title: 'Delegate widget child operation',
5535
+ scope: 'global',
5536
+ targetKind: 'childOperation',
5537
+ target: { kind: 'childOperation', resolver: 'widget-child-authoring-manifest-operation', ambiguityPolicy: 'fail', required: false },
5538
+ inputSchema: childOperationSchema,
5539
+ effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-child-operation-delegate', handlerContract: {
5540
+ reads: ['WidgetPageDefinition.widgets', 'ComponentDocMeta.configEditor', 'child ComponentAuthoringManifest', 'PAGE_BUILDER_WIDGET_AI_CATALOGS'],
5541
+ writes: ['page.widgets[].definition.inputs'],
5542
+ identityKeys: ['widgetKey', 'childComponentId', 'childOperationId'],
5543
+ inputSchema: childOperationSchema,
5544
+ failureModes: ['widget-not-found', 'child-manifest-missing', 'child-operation-unknown', 'local-child-input-write-attempted'],
5545
+ description: 'Delegates child widget input edits to the owning manifest or configEditor instead of redefining child semantics in page-builder.',
5546
+ } }],
5547
+ validators: ['widget-exists', 'child-manifest-available', 'child-operation-known', 'component-config-editor-respected', 'no-local-child-input-write'],
5548
+ affectedPaths: ['widgets[].definition.inputs'],
5549
+ submissionImpact: false,
5550
+ preconditions: ['page-loaded', 'target-widget-exists', 'child-authoring-contract-available'],
5551
+ },
5552
+ ],
5553
+ validators: [
5554
+ { validatorId: 'widget-page-definition-valid', level: 'error', code: 'PAGE_BUILDER_PAGE_VALID', description: 'The document must remain a renderable WidgetPageDefinition.' },
5555
+ { validatorId: 'no-legacy-grid-page-definition', level: 'error', code: 'PAGE_BUILDER_NO_GRID_PAGE', description: 'Legacy GridPageDefinition semantics cannot be reintroduced.' },
5556
+ { validatorId: 'page-context-json-valid', level: 'error', code: 'PAGE_BUILDER_CONTEXT_JSON', description: 'Page context must stay JSON-serializable.' },
5557
+ { validatorId: 'settings-panel-round-trip-valid', level: 'error', code: 'PAGE_BUILDER_SETTINGS_ROUND_TRIP', description: 'Settings panel apply/save/reset/reopen must preserve the same page shape.' },
5558
+ { validatorId: 'canvas-columns-integer', level: 'error', code: 'PAGE_BUILDER_CANVAS_COLUMNS', description: 'Canvas columns must be an integer grid count.' },
5559
+ { validatorId: 'canvas-row-unit-valid', level: 'error', code: 'PAGE_BUILDER_CANVAS_ROW_UNIT', description: 'Canvas row unit must be a positive layout unit.' },
5560
+ { validatorId: 'canvas-gap-valid', level: 'error', code: 'PAGE_BUILDER_CANVAS_GAP', description: 'Canvas gap must be a valid non-negative spacing value.' },
5561
+ { validatorId: 'canvas-items-reference-existing-widgets', level: 'error', code: 'PAGE_BUILDER_CANVAS_WIDGET_REFS', description: 'Every canvas item must reference an existing widgetKey.' },
5562
+ { validatorId: 'widget-key-unique', level: 'error', code: 'PAGE_BUILDER_WIDGET_KEY_UNIQUE', description: 'Widget keys must be unique.' },
5563
+ { validatorId: 'component-registered', level: 'error', code: 'PAGE_BUILDER_COMPONENT_REGISTERED', description: 'Widget component id must exist in the component registry/catalog.' },
5564
+ { validatorId: 'canvas-item-valid', level: 'error', code: 'PAGE_BUILDER_CANVAS_ITEM', description: 'Canvas item geometry must be valid.' },
5565
+ { validatorId: 'child-inputs-delegated', level: 'error', code: 'PAGE_BUILDER_CHILD_INPUTS_DELEGATED', description: 'Child component input edits must delegate to the child owner.' },
5566
+ { validatorId: 'widget-key-not-array-index', level: 'error', code: 'PAGE_BUILDER_WIDGET_KEY_INDEX', description: 'Array index is not a stable widget identity.' },
5567
+ { validatorId: 'widget-exists', level: 'error', code: 'PAGE_BUILDER_WIDGET_EXISTS', description: 'Target widget must exist.' },
5568
+ { validatorId: 'destructive-removal-confirmed', level: 'error', code: 'PAGE_BUILDER_DESTRUCTIVE_CONFIRMED', description: 'Destructive removals require explicit confirmation.' },
5569
+ { validatorId: 'composition-links-cleaned', level: 'error', code: 'PAGE_BUILDER_LINKS_CLEANED', description: 'Removing a widget must remove dangling composition links.' },
5570
+ { validatorId: 'canvas-item-targets-existing-widget', level: 'error', code: 'PAGE_BUILDER_CANVAS_ITEM_TARGET', description: 'Moved canvas item must target an existing widget.' },
5571
+ { validatorId: 'canvas-overlap-policy-valid', level: 'warning', code: 'PAGE_BUILDER_CANVAS_OVERLAP', description: 'Canvas overlap policy must be respected.' },
5572
+ { validatorId: 'widget-shell-shape-valid', level: 'error', code: 'PAGE_BUILDER_SHELL_SHAPE', description: 'Widget shell config must match the shell editor contract.' },
5573
+ { validatorId: 'settings-panel-bridge-available', level: 'error', code: 'PAGE_BUILDER_SETTINGS_BRIDGE', description: 'Settings panel bridge must be available for shell/page editors.' },
5574
+ { validatorId: 'child-inputs-not-mutated', level: 'error', code: 'PAGE_BUILDER_CHILD_INPUTS_NOT_MUTATED', description: 'Shell edits cannot mutate child inputs.' },
5575
+ { validatorId: 'composition-link-id-unique', level: 'error', code: 'PAGE_BUILDER_LINK_ID_UNIQUE', description: 'Composition link ids must be unique.' },
5576
+ { validatorId: 'composition-endpoints-resolve', level: 'error', code: 'PAGE_BUILDER_LINK_ENDPOINTS', description: 'Composition endpoints must resolve to known widgets, ports and nested paths.' },
5577
+ { validatorId: 'nested-path-terminal-widget-key-required', level: 'error', code: 'PAGE_BUILDER_NESTED_PATH_WIDGET_KEY', description: 'Nested component-port paths must terminate at a stable widget key.' },
5578
+ { validatorId: 'json-logic-condition-valid', level: 'error', code: 'PAGE_BUILDER_JSON_LOGIC', description: 'Composition link condition must be valid Json Logic.' },
5579
+ { validatorId: 'no-legacy-connections-write', level: 'error', code: 'PAGE_BUILDER_NO_LEGACY_CONNECTIONS', description: 'Operations must write page.composition.links, not legacy connections.' },
5580
+ { validatorId: 'composition-link-exists', level: 'error', code: 'PAGE_BUILDER_LINK_EXISTS', description: 'Target composition link must exist.' },
5581
+ { validatorId: 'composition-links-still-valid', level: 'error', code: 'PAGE_BUILDER_LINKS_STILL_VALID', description: 'Remaining composition links must still validate after the operation.' },
5582
+ { validatorId: 'ui-composition-plan-valid', level: 'error', code: 'PAGE_BUILDER_UI_PLAN_VALID', description: 'UiCompositionPlan must match the intermediate agentic plan contract.' },
5583
+ { validatorId: 'compiled-page-valid', level: 'error', code: 'PAGE_BUILDER_COMPILED_PAGE_VALID', description: 'Compiled output must be a valid WidgetPageDefinition.' },
5584
+ { validatorId: 'widget-keys-unique', level: 'error', code: 'PAGE_BUILDER_WIDGET_KEYS_UNIQUE', description: 'Compiled plan must produce unique widget keys.' },
5585
+ { validatorId: 'state-path-valid', level: 'error', code: 'PAGE_BUILDER_STATE_PATH', description: 'State paths must be valid page state paths.' },
5586
+ { validatorId: 'state-layer-valid', level: 'error', code: 'PAGE_BUILDER_STATE_LAYER', description: 'State layer must be page or session.' },
5587
+ { validatorId: 'state-json-valid', level: 'error', code: 'PAGE_BUILDER_STATE_JSON', description: 'State values must be JSON-serializable.' },
5588
+ { validatorId: 'composition-state-links-still-valid', level: 'error', code: 'PAGE_BUILDER_STATE_LINKS', description: 'State edits must not break composition links.' },
5589
+ { validatorId: 'preview-result-valid', level: 'error', code: 'PAGE_BUILDER_PREVIEW_VALID', description: 'Agentic preview result must be structurally valid.' },
5590
+ { validatorId: 'compiled-page-patch-present', level: 'error', code: 'PAGE_BUILDER_PATCH_PAGE_PRESENT', description: 'Preview envelope must include compiledFormPatch.patch.page.' },
5591
+ { validatorId: 'no-envelope-runtime-persistence', level: 'error', code: 'PAGE_BUILDER_NO_ENVELOPE_PERSISTENCE', description: 'Runtime persistence cannot save the full preview envelope.' },
5592
+ { validatorId: 'runtime-page-valid', level: 'error', code: 'PAGE_BUILDER_RUNTIME_PAGE_VALID', description: 'Runtime page must remain renderable by praxis-dynamic-page.' },
5593
+ { validatorId: 'page-identity-complete', level: 'error', code: 'PAGE_BUILDER_PAGE_IDENTITY', description: 'Save requires component type, component id and scope identity.' },
5594
+ { validatorId: 'etag-policy-valid', level: 'error', code: 'PAGE_BUILDER_ETAG_POLICY', description: 'Save must respect backend ETag/If-Match policy.' },
5595
+ { validatorId: 'child-manifest-available', level: 'error', code: 'PAGE_BUILDER_CHILD_MANIFEST_AVAILABLE', description: 'Delegated child component must expose an authoring manifest or configEditor.' },
5596
+ { validatorId: 'child-operation-known', level: 'error', code: 'PAGE_BUILDER_CHILD_OPERATION_KNOWN', description: 'Delegated child operation id must be known.' },
5597
+ { validatorId: 'component-config-editor-respected', level: 'error', code: 'PAGE_BUILDER_CONFIG_EDITOR_RESPECTED', description: 'ComponentDocMeta.configEditor must remain the source for child input editing.' },
5598
+ { validatorId: 'no-local-child-input-write', level: 'error', code: 'PAGE_BUILDER_NO_LOCAL_CHILD_INPUT_WRITE', description: 'Page-builder must not define child component input semantics locally.' },
5599
+ ],
5600
+ roundTripRequirements: [
5601
+ 'WidgetPageDefinition is the canonical persisted runtime page shape for page-builder.',
5602
+ 'UiCompositionPlan is an intermediate agentic plan and must compile before local preview/apply.',
5603
+ 'Widget keys and composition link ids are stable identities; array indexes are never canonical identities.',
5604
+ 'Persisted wiring uses page.composition.links and must not write legacy connections.',
5605
+ 'Nested component ports use component-port refs with nestedPath ending in a stable widget key.',
5606
+ 'Child widget definition.inputs edits delegate to the child manifest or ComponentDocMeta.configEditor.',
5607
+ 'Agentic preview applies only compiledFormPatch.patch.page to runtime; the full patch envelope is diagnostics/audit data.',
5608
+ 'Save uses pageIdentity and ETag policy from praxis-config-starter; pageIdentity is not persisted inside WidgetPageDefinition.',
5609
+ 'Settings panel apply/save/reset/reopen must preserve page settings and widget shell config without hidden normalization drift.',
5610
+ ],
5611
+ examples: [
5612
+ {
5613
+ id: 'configure-dashboard-page',
5614
+ request: 'Set this page title to Payroll Overview and store payrollPeriod in the page context.',
5615
+ operationId: 'page.configure',
5616
+ params: { title: 'Payroll Overview', context: { payrollPeriod: 'current' } },
5617
+ isPositive: true,
5618
+ },
5619
+ {
5620
+ id: 'configure-canvas-grid',
5621
+ request: 'Use a 12 column dashboard grid with compact gaps.',
5622
+ operationId: 'canvas.configure',
5623
+ target: 'canvas',
5624
+ params: { columns: 12, rowUnit: 8, gap: 8 },
5625
+ isPositive: true,
5626
+ },
5627
+ {
5628
+ id: 'add-employees-table-widget',
5629
+ request: 'Add an employees table widget to the left half of the first row.',
5630
+ operationId: 'widget.add',
5631
+ target: 'employees-table',
5632
+ params: { widgetKey: 'employees-table', componentId: 'praxis-table', canvasItem: { x: 0, y: 0, w: 6, h: 8 } },
5633
+ isPositive: true,
5634
+ },
5635
+ {
5636
+ id: 'remove-summary-card',
5637
+ request: 'Remove the summary-card widget and its links.',
5638
+ operationId: 'widget.remove',
5639
+ target: 'summary-card',
5640
+ params: { widgetKey: 'summary-card', confirmed: true },
5641
+ isPositive: true,
5642
+ },
5643
+ {
5644
+ id: 'move-payroll-chart',
5645
+ request: 'Move payroll-chart to the right column and make it taller.',
5646
+ operationId: 'widget.moveResize',
5647
+ target: 'payroll-chart',
5648
+ params: { widgetKey: 'payroll-chart', canvasItem: { x: 6, y: 0, w: 6, h: 10 } },
5649
+ isPositive: true,
5650
+ },
5651
+ {
5652
+ id: 'configure-widget-shell',
5653
+ request: 'Rename the employees widget card and enable the refresh shell action.',
5654
+ operationId: 'widget.shell.configure',
5655
+ target: 'employees-table',
5656
+ params: { widgetKey: 'employees-table', shell: { title: 'Employees', actions: [{ id: 'refresh' }] } },
5657
+ isPositive: true,
5658
+ },
5659
+ {
5660
+ id: 'link-department-selection-to-table',
5661
+ request: 'When department-list selection changes, filter employees-table by department id.',
5662
+ operationId: 'composition.link.add',
5663
+ target: 'department-selection-to-employees',
5664
+ params: { linkId: 'department-selection-to-employees', from: { widget: 'department-list', port: 'selection' }, to: { widget: 'employees-table', port: 'filter' } },
5665
+ isPositive: true,
5666
+ },
5667
+ {
5668
+ id: 'remove-obsolete-link',
5669
+ request: 'Remove the old payroll-filter-link composition link.',
5670
+ operationId: 'composition.link.remove',
5671
+ target: 'payroll-filter-link',
5672
+ params: { linkId: 'payroll-filter-link', confirmed: true },
5673
+ isPositive: true,
5674
+ },
5675
+ {
5676
+ id: 'compile-resource-dashboard-plan',
5677
+ request: 'Compile the resource-dashboard plan returned by the assistant into a renderable page preview.',
5678
+ operationId: 'composition.plan.compile',
5679
+ target: 'preview-payroll-dashboard',
5680
+ params: { uiCompositionPlan: { layoutPreset: 'resource-dashboard' } },
5681
+ isPositive: true,
5682
+ },
5683
+ {
5684
+ id: 'set-selected-department-state',
5685
+ request: 'Store selectedDepartmentId in page state for downstream links.',
5686
+ operationId: 'state.set',
5687
+ target: 'state.selectedDepartmentId',
5688
+ params: { path: 'selectedDepartmentId', value: 'sales', layer: 'page' },
5689
+ isPositive: true,
5690
+ },
5691
+ {
5692
+ id: 'apply-agentic-preview',
5693
+ request: 'Apply the current assistant preview to the local page.',
5694
+ operationId: 'page.preview.apply',
5695
+ target: 'preview-current',
5696
+ params: { previewId: 'preview-current' },
5697
+ isPositive: true,
5698
+ },
5699
+ {
5700
+ id: 'persist-page',
5701
+ request: 'Save this page configuration using the current ETag.',
5702
+ operationId: 'page.persist.save',
5703
+ target: 'tenant-dashboard-page',
5704
+ params: { pageIdentity: { componentType: 'page-builder', componentId: 'tenant-dashboard', scope: 'tenant' }, etag: '"v7"' },
5705
+ isPositive: true,
5706
+ },
5707
+ {
5708
+ id: 'delegate-table-column-edit',
5709
+ request: 'Add a salary column to the employees table widget.',
5710
+ operationId: 'childOperation.delegate',
5711
+ target: 'employees-table',
5712
+ params: { widgetKey: 'employees-table', childComponentId: 'praxis-table', childOperationId: 'column.add', reason: 'TableConfig is owned by praxis-table.' },
5713
+ isPositive: true,
5714
+ },
5715
+ {
5716
+ id: 'reject-direct-dynamic-form-field-write',
5717
+ request: 'Directly write a new dynamic-form field into the widget inputs from page-builder.',
5718
+ operationId: 'childOperation.delegate',
5719
+ target: 'employee-form',
5720
+ params: { widgetKey: 'employee-form', childComponentId: 'praxis-dynamic-form', childOperationId: 'field.add', reason: 'Dynamic form fields are delegated.' },
5721
+ isPositive: false,
5722
+ },
5723
+ {
5724
+ id: 'reject-legacy-connections',
5725
+ request: 'Create the widget link under the old connections collection.',
5726
+ operationId: 'composition.link.add',
5727
+ target: 'legacy-connection',
5728
+ params: { linkId: 'legacy-connection', collection: 'connections' },
5729
+ isPositive: false,
5730
+ },
5731
+ ],
5732
+ };
5733
+
5070
5734
  function compileUiCompositionPlan(plan) {
5071
5735
  const diagnostics = validateUiCompositionPlan(plan);
5072
5736
  if (diagnostics.length) {
@@ -5080,8 +5744,8 @@ function compileUiCompositionPlan(plan) {
5080
5744
  valid: true,
5081
5745
  page: {
5082
5746
  layoutPreset: plan.layoutPreset,
5083
- canvas: clone(plan.canvas),
5084
- state: clone(plan.state),
5747
+ canvas: clone$1(plan.canvas),
5748
+ state: clone$1(plan.state),
5085
5749
  widgets: plan.widgets.map(toWidgetInstance),
5086
5750
  composition: {
5087
5751
  version: '1.0.0',
@@ -5176,6 +5840,7 @@ function validateEndpoint(endpoint, widgetKeys, diagnostics, path) {
5176
5840
  path: `${path}.port`,
5177
5841
  });
5178
5842
  }
5843
+ validateNestedPath(endpoint.nestedPath, diagnostics, `${path}.nestedPath`);
5179
5844
  return;
5180
5845
  }
5181
5846
  if (!endpoint.path?.trim()) {
@@ -5186,14 +5851,26 @@ function validateEndpoint(endpoint, widgetKeys, diagnostics, path) {
5186
5851
  });
5187
5852
  }
5188
5853
  }
5854
+ function validateNestedPath(nestedPath, diagnostics, path) {
5855
+ if (!nestedPath?.length)
5856
+ return;
5857
+ const terminal = nestedPath[nestedPath.length - 1];
5858
+ if (terminal?.kind !== 'widget' || !terminal.key?.trim()) {
5859
+ diagnostics.push({
5860
+ code: 'endpoint-nested-terminal-widget-key-required',
5861
+ message: 'Nested component endpoint must end with a widget segment with a stable key.',
5862
+ path,
5863
+ });
5864
+ }
5865
+ }
5189
5866
  function toWidgetInstance(widget) {
5190
5867
  return {
5191
5868
  key: widget.key,
5192
5869
  definition: {
5193
5870
  id: widget.componentId,
5194
5871
  bindingOrder: widget.bindingOrder,
5195
- inputs: clone(widget.inputs ?? {}),
5196
- outputs: clone(widget.outputs ?? {}),
5872
+ inputs: clone$1(widget.inputs ?? {}),
5873
+ outputs: clone$1(widget.outputs ?? {}),
5197
5874
  },
5198
5875
  };
5199
5876
  }
@@ -5204,10 +5881,10 @@ function toCompositionLink(binding) {
5204
5881
  from: toCompositionEndpoint(binding.from),
5205
5882
  to: toCompositionEndpoint(binding.to),
5206
5883
  intent: binding.intent,
5207
- condition: clone(binding.condition),
5884
+ condition: clone$1(binding.condition),
5208
5885
  transform,
5209
- policy: clone(binding.policy),
5210
- metadata: clone(binding.metadata),
5886
+ policy: clone$1(binding.policy),
5887
+ metadata: clone$1(binding.metadata),
5211
5888
  };
5212
5889
  }
5213
5890
  function toCompositionEndpoint(endpoint) {
@@ -5218,6 +5895,7 @@ function toCompositionEndpoint(endpoint) {
5218
5895
  widget: endpoint.widget,
5219
5896
  port: endpoint.port,
5220
5897
  direction: endpoint.direction,
5898
+ ...(endpoint.nestedPath?.length ? { nestedPath: clone$1(endpoint.nestedPath) } : {}),
5221
5899
  },
5222
5900
  };
5223
5901
  }
@@ -5265,7 +5943,7 @@ function toCompositionTransform(transform) {
5265
5943
  phase: 'link-propagation',
5266
5944
  input: { source: transform.inputSource ?? 'event' },
5267
5945
  config: {
5268
- template: clone(transform.template),
5946
+ template: clone$1(transform.template),
5269
5947
  },
5270
5948
  },
5271
5949
  ],
@@ -5282,7 +5960,7 @@ function toCompositionTransform(transform) {
5282
5960
  kind: 'constant',
5283
5961
  phase: 'link-propagation',
5284
5962
  config: {
5285
- value: clone(transform.value),
5963
+ value: clone$1(transform.value),
5286
5964
  },
5287
5965
  },
5288
5966
  ],
@@ -5305,7 +5983,7 @@ function toCompositionTransform(transform) {
5305
5983
  ],
5306
5984
  };
5307
5985
  }
5308
- function clone(value) {
5986
+ function clone$1(value) {
5309
5987
  if (value == null || typeof value !== 'object')
5310
5988
  return value;
5311
5989
  try {
@@ -5387,7 +6065,11 @@ class PageBuilderAiAdapter {
5387
6065
  error: 'CompiledFormPatch must contain patch.page.',
5388
6066
  };
5389
6067
  }
5390
- return this.applyPatch({ page }, 'agentic-authoring.compiled-form-patch');
6068
+ const materializedPage = this.clone(page);
6069
+ if (this.shouldPreserveLocalTransientFields(compiledFormPatch)) {
6070
+ this.preserveLocalTransientFields(materializedPage);
6071
+ }
6072
+ return this.applyPatch({ page: materializedPage }, 'agentic-authoring.compiled-form-patch');
5391
6073
  }
5392
6074
  async applyUiCompositionPlan(plan) {
5393
6075
  const compiled = compileUiCompositionPlan(plan);
@@ -5526,6 +6208,76 @@ class PageBuilderAiAdapter {
5526
6208
  }
5527
6209
  return merged;
5528
6210
  }
6211
+ shouldPreserveLocalTransientFields(compiledFormPatch) {
6212
+ const warnings = compiledFormPatch['warnings'];
6213
+ return Array.isArray(warnings) && warnings.includes('server-backed-field-labels-customized-locally');
6214
+ }
6215
+ preserveLocalTransientFields(patchPage) {
6216
+ const basePage = this.normalizePageForAi(this.parsePage(this.host.page));
6217
+ const baseWidgets = basePage?.widgets ?? [];
6218
+ for (const patchWidget of patchPage.widgets ?? []) {
6219
+ if (patchWidget.definition?.id !== 'praxis-dynamic-form')
6220
+ continue;
6221
+ const key = this.getWidgetKey(patchWidget);
6222
+ if (!key)
6223
+ continue;
6224
+ const baseWidget = baseWidgets.find((widget) => this.getWidgetKey(widget) === key);
6225
+ if (!baseWidget)
6226
+ continue;
6227
+ this.preserveLocalTransientFieldsForWidget(baseWidget, patchWidget);
6228
+ }
6229
+ }
6230
+ preserveLocalTransientFieldsForWidget(baseWidget, patchWidget) {
6231
+ const baseConfig = (baseWidget.definition?.inputs?.['config'] ?? {});
6232
+ const patchInputs = patchWidget.definition.inputs ??= {};
6233
+ const patchConfig = (patchInputs['config'] ??= {});
6234
+ const baseFields = Array.isArray(baseConfig['fieldMetadata']) ? baseConfig['fieldMetadata'] : [];
6235
+ const patchFields = Array.isArray(patchConfig['fieldMetadata'])
6236
+ ? patchConfig['fieldMetadata']
6237
+ : (patchConfig['fieldMetadata'] = []);
6238
+ const preservedNames = [];
6239
+ for (const field of baseFields) {
6240
+ if (!this.isLocalTransientField(field) || patchFields.some((entry) => entry?.name === field.name))
6241
+ continue;
6242
+ patchFields.push(this.clone(field));
6243
+ preservedNames.push(field.name);
6244
+ }
6245
+ if (preservedNames.length === 0)
6246
+ return;
6247
+ const baseSections = Array.isArray(baseConfig['sections']) ? baseConfig['sections'] : [];
6248
+ const patchSections = Array.isArray(patchConfig['sections'])
6249
+ ? patchConfig['sections']
6250
+ : (patchConfig['sections'] = []);
6251
+ for (const section of baseSections) {
6252
+ if (!this.sectionContainsAnyField(section, preservedNames))
6253
+ continue;
6254
+ if (this.sectionContainsAnyField({ rows: [{ columns: [{ fields: this.flattenSectionFields(patchSections) }] }] }, preservedNames)) {
6255
+ continue;
6256
+ }
6257
+ patchSections.push(this.clone(section));
6258
+ }
6259
+ }
6260
+ isLocalTransientField(field) {
6261
+ return field?.source === 'local' || field?.transient === true || field?.submitPolicy === 'omit';
6262
+ }
6263
+ sectionContainsAnyField(section, names) {
6264
+ const fields = this.flattenSectionFields([section]);
6265
+ return names.some((name) => fields.includes(name));
6266
+ }
6267
+ flattenSectionFields(sections) {
6268
+ const fields = [];
6269
+ for (const section of sections) {
6270
+ for (const row of section?.rows ?? []) {
6271
+ for (const column of row?.columns ?? []) {
6272
+ for (const field of column?.fields ?? []) {
6273
+ if (typeof field === 'string')
6274
+ fields.push(field);
6275
+ }
6276
+ }
6277
+ }
6278
+ }
6279
+ return fields;
6280
+ }
5529
6281
  mergeLinks(baseLinks, patchLinks) {
5530
6282
  const base = baseLinks || [];
5531
6283
  const patch = patchLinks || [];
@@ -5646,20 +6398,37 @@ class PageBuilderAiAdapter {
5646
6398
  linkKey(link) {
5647
6399
  if (!link)
5648
6400
  return null;
5649
- const from = link.from.kind === 'component-port'
5650
- ? (link.from.ref.widget && link.from.ref.port
5651
- ? `widget:${link.from.ref.widget}::${link.from.ref.port}::${link.from.ref.direction}`
5652
- : null)
5653
- : (link.from.ref.path ? `state:${link.from.ref.path}::${link.from.ref.layer || 'values'}` : null);
5654
- const to = link.to.kind === 'component-port'
5655
- ? (link.to.ref.widget && link.to.ref.port
5656
- ? `widget:${link.to.ref.widget}::${link.to.ref.port}::${link.to.ref.direction}`
5657
- : null)
5658
- : (link.to.ref.path ? `state:${link.to.ref.path}::${link.to.ref.layer || 'values'}` : null);
6401
+ const from = this.endpointKey(link.from);
6402
+ const to = this.endpointKey(link.to);
5659
6403
  if (!from || !to)
5660
6404
  return null;
5661
6405
  return `${from}->${to}`;
5662
6406
  }
6407
+ endpointKey(endpoint) {
6408
+ if (endpoint.kind === 'component-port') {
6409
+ if (!endpoint.ref.widget || !endpoint.ref.port)
6410
+ return null;
6411
+ return [
6412
+ 'widget',
6413
+ endpoint.ref.widget,
6414
+ endpoint.ref.port,
6415
+ endpoint.ref.direction,
6416
+ this.nestedPathKey(endpoint.ref.nestedPath),
6417
+ ].filter((part) => part !== '').join('::');
6418
+ }
6419
+ return endpoint.ref.path ? `state:${endpoint.ref.path}::${endpoint.ref.layer || 'values'}` : null;
6420
+ }
6421
+ nestedPathKey(nestedPath) {
6422
+ if (!nestedPath?.length)
6423
+ return '';
6424
+ return `nested:${JSON.stringify(nestedPath.map((segment) => ({
6425
+ kind: segment.kind,
6426
+ id: segment.id,
6427
+ key: segment.key,
6428
+ index: segment.index,
6429
+ componentType: segment.componentType,
6430
+ })))}`;
6431
+ }
5663
6432
  pickLayoutOptions(options) {
5664
6433
  if (!options)
5665
6434
  return undefined;
@@ -5717,42 +6486,60 @@ class PageBuilderAgenticAuthoringTurnFlow {
5717
6486
  this.service = service;
5718
6487
  this.context = context;
5719
6488
  }
5720
- async submit(request) {
6489
+ submit(request) {
5721
6490
  const prompt = request.prompt?.trim();
5722
6491
  if (!prompt) {
5723
- return {
6492
+ return Promise.resolve({
5724
6493
  state: 'listening',
5725
6494
  phase: 'capture',
5726
- };
6495
+ });
5727
6496
  }
6497
+ if (this.isResourceDiscoveryTool(request)) {
6498
+ return this.handleResourceDiscoveryTool(request, prompt);
6499
+ }
6500
+ if (this.context.enableTurnStream?.() === true && this.service.streamTurn) {
6501
+ return this.submitWithTurnStream(request, prompt);
6502
+ }
6503
+ return this.submitSynchronously(request, prompt);
6504
+ }
6505
+ async submitSynchronously(request, prompt) {
5728
6506
  try {
5729
6507
  const authoringContext = this.buildAuthoringContext(request);
5730
6508
  const componentCapabilities = await this.context.loadComponentCapabilities();
5731
6509
  const selectedWidgetKey = this.context.selectedWidgetKey();
5732
- const intentResolution = await firstValueFrom(this.service.resolveIntent({
5733
- userPrompt: prompt,
5734
- targetApp: this.context.targetApp,
5735
- targetComponentId: this.context.targetComponentId,
5736
- currentPage: this.context.currentPage(),
5737
- selectedWidgetKey,
5738
- provider: this.context.provider(),
5739
- model: this.context.model(),
5740
- apiKey: this.context.apiKey(),
5741
- componentCapabilities,
5742
- ...authoringContext,
5743
- }));
6510
+ const intentResolution = await firstValueFrom(this.service.resolveIntent(this.buildIntentResolutionRequest(prompt, selectedWidgetKey, componentCapabilities, authoringContext)));
5744
6511
  const intentAssistantMessage = this.resolveIntentAssistantMessage(intentResolution);
5745
6512
  if (intentAssistantMessage) {
6513
+ const quickReplies = this.toShellQuickReplies(intentResolution.quickReplies ?? []);
6514
+ const explicitPendingClarification = this.toShellPendingClarification(intentResolution.pendingClarification);
6515
+ const intentClarification = this.resolveIntentClarification(intentResolution);
6516
+ const pendingClarification = explicitPendingClarification
6517
+ ?? (intentClarification
6518
+ ? this.buildPendingClarificationForNextTurn(request, this.resolveEffectivePrompt(intentResolution, prompt), intentClarification)
6519
+ : null);
6520
+ if (!this.hasPendingClarificationQuestion(pendingClarification) && quickReplies.length === 0) {
6521
+ return {
6522
+ state: 'success',
6523
+ phase: 'summarize',
6524
+ assistantMessage: intentAssistantMessage,
6525
+ quickReplies,
6526
+ canApply: false,
6527
+ statusText: '',
6528
+ errorText: '',
6529
+ preview: null,
6530
+ diagnostics: { intentResolution },
6531
+ };
6532
+ }
5746
6533
  return {
5747
6534
  state: 'clarification',
5748
6535
  phase: 'clarify',
5749
6536
  assistantMessage: intentAssistantMessage,
5750
- quickReplies: this.toShellQuickReplies(intentResolution.quickReplies ?? []),
6537
+ quickReplies,
5751
6538
  canApply: false,
5752
6539
  statusText: '',
5753
6540
  errorText: '',
5754
6541
  preview: null,
5755
- pendingClarification: this.resolvePendingClarificationForNextTurn(intentResolution, request, this.resolveEffectivePrompt(intentResolution, prompt), intentAssistantMessage),
6542
+ pendingClarification,
5756
6543
  diagnostics: { intentResolution },
5757
6544
  };
5758
6545
  }
@@ -5827,6 +6614,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
5827
6614
  state: 'review',
5828
6615
  phase: 'review',
5829
6616
  assistantMessage: status,
6617
+ quickReplies: [],
5830
6618
  canApply: true,
5831
6619
  statusText: status,
5832
6620
  errorText: '',
@@ -5847,6 +6635,11 @@ class PageBuilderAgenticAuthoringTurnFlow {
5847
6635
  };
5848
6636
  }
5849
6637
  }
6638
+ submitWithTurnStream(request, prompt) {
6639
+ return from(this.buildTurnStreamRequest(request, prompt)).pipe(concatMap((streamRequest) => this.service.streamTurn(streamRequest)), concatMap((event) => from(this.toTurnResultFromStreamEvent(event))), catchError((error) => this.shouldFallbackFromTurnStreamError(error)
6640
+ ? from(this.submitSynchronously(request, prompt))
6641
+ : of(this.toTurnStreamTransportErrorResult(error))));
6642
+ }
5850
6643
  async cancel() {
5851
6644
  return {
5852
6645
  state: 'listening',
@@ -5860,10 +6653,413 @@ class PageBuilderAgenticAuthoringTurnFlow {
5860
6653
  pendingPatch: null,
5861
6654
  };
5862
6655
  }
6656
+ async buildTurnStreamRequest(request, prompt) {
6657
+ const componentCapabilities = await this.context.loadComponentCapabilities();
6658
+ const selectedWidgetKey = this.context.selectedWidgetKey();
6659
+ return {
6660
+ userPrompt: prompt,
6661
+ targetApp: this.context.targetApp,
6662
+ targetComponentId: this.context.targetComponentId,
6663
+ currentRoute: null,
6664
+ currentPage: this.context.currentPage(),
6665
+ selectedWidgetKey,
6666
+ provider: this.context.provider(),
6667
+ model: this.context.model(),
6668
+ apiKey: this.context.apiKey(),
6669
+ componentCapabilities,
6670
+ ...this.buildAuthoringContext(request),
6671
+ };
6672
+ }
6673
+ async toTurnResultFromStreamEvent(event) {
6674
+ const payload = this.toJsonObject(event.payload) ?? {};
6675
+ if (event.type === 'result') {
6676
+ return this.toResultTurnFromStreamPayload(payload);
6677
+ }
6678
+ if (event.type === 'error') {
6679
+ const message = this.describeStreamError(payload);
6680
+ return {
6681
+ state: 'error',
6682
+ phase: 'contextualize',
6683
+ assistantMessage: message,
6684
+ canApply: false,
6685
+ statusText: '',
6686
+ errorText: message,
6687
+ preview: null,
6688
+ diagnostics: { streamError: payload },
6689
+ };
6690
+ }
6691
+ if (event.type === 'cancelled') {
6692
+ return {
6693
+ state: 'listening',
6694
+ phase: 'capture',
6695
+ assistantMessage: this.context.tx('agentic.status.cancelled', 'Request cancelled.'),
6696
+ quickReplies: [],
6697
+ canApply: false,
6698
+ statusText: '',
6699
+ errorText: '',
6700
+ preview: null,
6701
+ pendingPatch: null,
6702
+ };
6703
+ }
6704
+ return {
6705
+ state: 'processing',
6706
+ phase: this.phaseForStreamPayload(payload),
6707
+ assistantMessage: undefined,
6708
+ canApply: false,
6709
+ statusText: this.statusForStreamPayload(payload),
6710
+ errorText: '',
6711
+ preview: null,
6712
+ };
6713
+ }
6714
+ async toResultTurnFromStreamPayload(payload) {
6715
+ const intentResolution = payload['intentResolution'];
6716
+ const preview = payload['preview'];
6717
+ if (!intentResolution) {
6718
+ const message = this.context.tx('agentic.errors.intentResolution', 'Intent could not be resolved.');
6719
+ return {
6720
+ state: 'error',
6721
+ phase: 'contextualize',
6722
+ assistantMessage: message,
6723
+ canApply: false,
6724
+ statusText: '',
6725
+ errorText: message,
6726
+ preview: null,
6727
+ };
6728
+ }
6729
+ const assistantMessage = this.readString(payload, 'assistantMessage')
6730
+ || this.resolveIntentAssistantMessage(intentResolution)
6731
+ || (preview ? this.context.describePreviewStatus(preview) : '');
6732
+ const quickReplies = Array.isArray(payload['quickReplies'])
6733
+ ? this.toShellQuickReplies(payload['quickReplies'])
6734
+ : [];
6735
+ const canApply = payload['canApply'] === true && !!preview?.valid;
6736
+ if (!canApply) {
6737
+ const pendingClarification = this.toShellPendingClarification(intentResolution?.pendingClarification);
6738
+ const requiresChoice = quickReplies.length > 0;
6739
+ return {
6740
+ state: pendingClarification || requiresChoice ? 'clarification' : 'success',
6741
+ phase: pendingClarification || requiresChoice ? 'clarify' : 'summarize',
6742
+ assistantMessage,
6743
+ quickReplies,
6744
+ canApply: false,
6745
+ statusText: '',
6746
+ errorText: '',
6747
+ preview: null,
6748
+ pendingClarification,
6749
+ diagnostics: { intentResolution, preview },
6750
+ };
6751
+ }
6752
+ const applied = await this.context.applyLocalPreview(preview);
6753
+ if (!applied.success) {
6754
+ const message = applied.error || this.context.tx('agentic.errors.applyLocal', 'Preview could not be applied.');
6755
+ return {
6756
+ state: 'error',
6757
+ phase: 'preview',
6758
+ assistantMessage: message,
6759
+ canApply: false,
6760
+ statusText: '',
6761
+ errorText: message,
6762
+ preview: null,
6763
+ diagnostics: { intentResolution, preview },
6764
+ };
6765
+ }
6766
+ const status = assistantMessage || this.context.describePreviewStatus(preview);
6767
+ return {
6768
+ state: 'review',
6769
+ phase: 'review',
6770
+ assistantMessage: status,
6771
+ quickReplies: [],
6772
+ canApply: true,
6773
+ statusText: status,
6774
+ errorText: '',
6775
+ preview,
6776
+ diagnostics: { intentResolution, preview },
6777
+ };
6778
+ }
6779
+ phaseForStreamPayload(payload) {
6780
+ const phase = this.readString(payload, 'phase');
6781
+ if (phase === 'intent.resolve')
6782
+ return 'contextualize';
6783
+ if (phase === 'resource.discovery')
6784
+ return 'contextualize';
6785
+ if (phase.startsWith('preview.'))
6786
+ return 'preview';
6787
+ if (phase === 'review')
6788
+ return 'review';
6789
+ return 'contextualize';
6790
+ }
6791
+ statusForStreamPayload(payload) {
6792
+ const phase = this.readString(payload, 'phase');
6793
+ switch (phase) {
6794
+ case 'context.bundle':
6795
+ return this.context.tx('agentic.status.contextBundle', 'Preparing context...');
6796
+ case 'intent.resolve':
6797
+ if (this.isSecondPassStreamPayload(payload)) {
6798
+ return this.context.tx('agentic.status.refinedCandidates', 'Reviewing the retrieved resources with the AI...');
6799
+ }
6800
+ return this.context.tx('agentic.status.resolvingIntent', 'Resolving intent...');
6801
+ case 'resource.discovery':
6802
+ if (this.isBackendResourceDiscoveryPayload(payload)) {
6803
+ return this.context.tx('agentic.status.resourceDiscoveryBackend', 'Found API resources in the backend catalog.');
6804
+ }
6805
+ return this.context.tx('agentic.status.resourceDiscovery', 'Finding API resources...');
6806
+ case 'preview.plan':
6807
+ return this.context.tx('agentic.status.previewing', 'Generating preview...');
6808
+ case 'preview.compile':
6809
+ return this.context.tx('agentic.status.previewCompile', 'Compiling preview...');
6810
+ default:
6811
+ return this.readString(payload, 'summary') || this.readString(payload, 'message');
6812
+ }
6813
+ }
6814
+ isBackendResourceDiscoveryPayload(payload) {
6815
+ const diagnostics = this.toJsonObject(payload['diagnostics']);
6816
+ return diagnostics?.['source'] === 'backend-resource-catalog';
6817
+ }
6818
+ isSecondPassStreamPayload(payload) {
6819
+ const diagnostics = this.toJsonObject(payload['diagnostics']);
6820
+ return diagnostics?.['secondPass'] === true;
6821
+ }
6822
+ describeStreamError(payload) {
6823
+ const code = this.readString(payload, 'code');
6824
+ if (code === 'agentic-authoring-timeout') {
6825
+ return this.context.tx('agentic.errors.streamTimeout', 'The assistant took too long to finish this request. Try again with a narrower request or review the active context.');
6826
+ }
6827
+ if (code === 'agentic-authoring-processing-failed') {
6828
+ return this.context.tx('agentic.errors.streamProcessing', 'The assistant could not finish this authoring request. Try again or ask support to review the diagnostics.');
6829
+ }
6830
+ return this.readString(payload, 'assistantMessage')
6831
+ || this.context.tx('agentic.errors.generic', 'AI authoring failed.');
6832
+ }
6833
+ toTurnStreamTransportErrorResult(error) {
6834
+ const message = this.isTurnStreamConnectionError(error)
6835
+ ? this.context.tx('agentic.errors.streamConnection', 'The assistant stream was interrupted before the turn finished. Try again or ask support to check the stream connection.')
6836
+ : this.context.describeError(error);
6837
+ return {
6838
+ state: 'error',
6839
+ phase: 'contextualize',
6840
+ assistantMessage: message,
6841
+ canApply: false,
6842
+ statusText: '',
6843
+ errorText: message,
6844
+ preview: null,
6845
+ diagnostics: { streamTransportError: this.toJsonObject(error) ?? { reason: 'stream-transport-error' } },
6846
+ };
6847
+ }
6848
+ shouldFallbackFromTurnStreamError(error) {
6849
+ if (this.isTurnStreamConnectionError(error)) {
6850
+ return false;
6851
+ }
6852
+ const status = this.readErrorStatus(error);
6853
+ if (status === 401 || status === 403) {
6854
+ return false;
6855
+ }
6856
+ return status === 404 || status === 501 || status === 503;
6857
+ }
6858
+ isTurnStreamConnectionError(error) {
6859
+ return this.toJsonObject(error)?.['praxisAgenticTurnStreamConnectionError'] === true;
6860
+ }
6861
+ readErrorStatus(error) {
6862
+ const status = this.toJsonObject(error)?.['status'];
6863
+ return typeof status === 'number' && Number.isFinite(status) ? status : null;
6864
+ }
6865
+ async handleResourceDiscoveryTool(request, prompt) {
6866
+ const contextHints = this.toJsonObject(request.action?.contextHints) ?? {};
6867
+ const retrievalQuery = this.readString(contextHints, 'retrievalQuery') || prompt;
6868
+ const artifactKind = this.readString(contextHints, 'artifactKind') || null;
6869
+ const limit = this.readNumber(contextHints, 'limit');
6870
+ const result = await firstValueFrom(this.service.searchResourceCandidates({
6871
+ retrievalQuery,
6872
+ userPrompt: prompt,
6873
+ artifactKind,
6874
+ limit,
6875
+ }));
6876
+ const quickReplies = result.quickReplies?.length
6877
+ ? this.toShellQuickReplies(result.quickReplies)
6878
+ : this.toResourceCandidateQuickReplies(result.candidates, result.artifactKind);
6879
+ const assistantMessage = result.assistantMessage?.trim()
6880
+ || (quickReplies.length
6881
+ ? this.context.tx('agentic.resourceDiscovery.found', 'I found APIs that can feed this screen. Choose one before generating the preview.')
6882
+ : this.context.tx('agentic.resourceDiscovery.empty', 'I did not find a matching API yet. Describe the business data this screen should use.'));
6883
+ const intentResult = await this.resolveIntentAfterResourceDiscovery(request, request.pendingClarification?.sourcePrompt || prompt, contextHints, result, assistantMessage, quickReplies);
6884
+ if (intentResult) {
6885
+ return intentResult;
6886
+ }
6887
+ return {
6888
+ state: 'clarification',
6889
+ phase: 'clarify',
6890
+ assistantMessage,
6891
+ quickReplies,
6892
+ canApply: false,
6893
+ statusText: '',
6894
+ errorText: '',
6895
+ preview: null,
6896
+ pendingClarification: {
6897
+ sourcePrompt: request.pendingClarification?.sourcePrompt || prompt,
6898
+ assistantMessage,
6899
+ questions: [
6900
+ {
6901
+ id: 'resource',
6902
+ type: 'text',
6903
+ label: assistantMessage,
6904
+ },
6905
+ ],
6906
+ clientTurnId: request.clientTurnId,
6907
+ diagnostics: {
6908
+ resourceDiscovery: {
6909
+ tool: result.tool,
6910
+ retrievalQuery: result.retrievalQuery,
6911
+ artifactKind: result.artifactKind,
6912
+ warnings: result.warnings,
6913
+ },
6914
+ },
6915
+ },
6916
+ diagnostics: { resourceDiscovery: result },
6917
+ };
6918
+ }
6919
+ async resolveIntentAfterResourceDiscovery(request, prompt, contextHints, result, fallbackAssistantMessage, fallbackQuickReplies) {
6920
+ if (!result.valid || !result.candidates?.length) {
6921
+ return null;
6922
+ }
6923
+ const componentCapabilities = await this.context.loadComponentCapabilities();
6924
+ const authoringContext = this.buildAuthoringContext(request);
6925
+ const intentResolution = await firstValueFrom(this.service.resolveIntent(this.buildIntentResolutionRequest(prompt, this.context.selectedWidgetKey(), componentCapabilities, {
6926
+ ...authoringContext,
6927
+ contextHints: this.resourceDiscoveryContextHints(contextHints, result),
6928
+ })));
6929
+ const assistantMessage = this.resolveIntentAssistantMessage(intentResolution)
6930
+ || this.resolveIntentClarification(intentResolution)
6931
+ || fallbackAssistantMessage;
6932
+ const quickReplies = intentResolution.quickReplies?.length
6933
+ ? this.toShellQuickReplies(intentResolution.quickReplies)
6934
+ : fallbackQuickReplies;
6935
+ const explicitPendingClarification = this.toShellPendingClarification(intentResolution.pendingClarification);
6936
+ const pendingClarification = explicitPendingClarification
6937
+ ?? this.buildPendingClarificationForNextTurn(request, this.resolveEffectivePrompt(intentResolution, prompt), assistantMessage);
6938
+ if (!assistantMessage && quickReplies.length === 0) {
6939
+ return null;
6940
+ }
6941
+ return {
6942
+ state: quickReplies.length > 0 || this.hasPendingClarificationQuestion(pendingClarification)
6943
+ ? 'clarification'
6944
+ : 'success',
6945
+ phase: quickReplies.length > 0 || this.hasPendingClarificationQuestion(pendingClarification)
6946
+ ? 'clarify'
6947
+ : 'summarize',
6948
+ assistantMessage,
6949
+ quickReplies,
6950
+ canApply: false,
6951
+ statusText: '',
6952
+ errorText: '',
6953
+ preview: null,
6954
+ pendingClarification,
6955
+ diagnostics: { resourceDiscovery: result, intentResolution },
6956
+ };
6957
+ }
6958
+ resourceDiscoveryContextHints(contextHints, result) {
6959
+ const { tool: _tool, ...baseContextHints } = contextHints;
6960
+ return {
6961
+ ...baseContextHints,
6962
+ resourceDiscovery: {
6963
+ tool: result.tool,
6964
+ retrievalQuery: result.retrievalQuery,
6965
+ artifactKind: result.artifactKind,
6966
+ assistantMessage: result.assistantMessage ?? null,
6967
+ candidates: result.candidates.map((candidate) => ({
6968
+ resourcePath: candidate.resourcePath,
6969
+ operation: candidate.operation,
6970
+ schemaUrl: candidate.schemaUrl,
6971
+ submitUrl: candidate.submitUrl,
6972
+ submitMethod: candidate.submitMethod,
6973
+ score: candidate.score,
6974
+ reason: candidate.reason,
6975
+ evidence: candidate.evidence,
6976
+ })),
6977
+ quickReplies: (result.quickReplies ?? []).map((reply) => ({
6978
+ id: reply.id,
6979
+ kind: reply.kind,
6980
+ label: reply.label,
6981
+ prompt: reply.prompt,
6982
+ description: reply.description ?? null,
6983
+ icon: reply.icon ?? null,
6984
+ tone: reply.tone ?? null,
6985
+ contextHints: this.toJsonObject(reply.contextHints) ?? null,
6986
+ })),
6987
+ warnings: result.warnings,
6988
+ },
6989
+ };
6990
+ }
6991
+ isResourceDiscoveryTool(request) {
6992
+ const contextHints = this.toJsonObject(request.action?.contextHints);
6993
+ return this.readString(contextHints, 'tool') === 'searchApiResources';
6994
+ }
6995
+ toResourceCandidateQuickReplies(candidates, artifactKind) {
6996
+ return candidates
6997
+ .filter((candidate) => !!candidate.resourcePath?.trim())
6998
+ .map((candidate) => {
6999
+ const resourcePath = candidate.resourcePath.trim();
7000
+ const submitUrl = candidate.submitUrl?.trim() || resourcePath;
7001
+ return {
7002
+ id: this.resourceCandidateId(resourcePath),
7003
+ kind: 'suggestion',
7004
+ label: this.resourceCandidateLabel(resourcePath),
7005
+ prompt: this.formatResourceCandidatePrompt(resourcePath),
7006
+ description: this.resourceCandidateDescription(candidate),
7007
+ icon: 'dataset',
7008
+ tone: 'resource',
7009
+ contextHints: {
7010
+ resourcePath,
7011
+ submitUrl,
7012
+ operation: candidate.operation,
7013
+ schemaUrl: candidate.schemaUrl,
7014
+ submitMethod: candidate.submitMethod,
7015
+ artifactKind,
7016
+ },
7017
+ };
7018
+ });
7019
+ }
7020
+ formatResourceCandidatePrompt(resourcePath) {
7021
+ return this.context
7022
+ .tx('agentic.resourceDiscovery.useResource', 'Use {resourcePath}')
7023
+ .replace('{resourcePath}', resourcePath);
7024
+ }
7025
+ resourceCandidateId(resourcePath) {
7026
+ const slug = resourcePath
7027
+ .toLowerCase()
7028
+ .replace(/[^a-z0-9]+/g, '-')
7029
+ .replace(/^-+|-+$/g, '');
7030
+ return slug ? `resource-${slug}` : 'resource-candidate';
7031
+ }
7032
+ resourceCandidateLabel(resourcePath) {
7033
+ const segment = resourcePath.split('/').filter((part) => !!part).pop() || resourcePath;
7034
+ const label = segment
7035
+ .replace(/[-_]+/g, ' ')
7036
+ .replace(/\s+/g, ' ')
7037
+ .trim();
7038
+ return label
7039
+ ? label.charAt(0).toUpperCase() + label.slice(1)
7040
+ : resourcePath;
7041
+ }
7042
+ resourceCandidateDescription(candidate) {
7043
+ const method = candidate.submitMethod?.trim();
7044
+ const url = candidate.submitUrl?.trim();
7045
+ return method && url ? `${method} ${url}` : url || undefined;
7046
+ }
5863
7047
  resolveIntentAssistantMessage(intentResolution) {
7048
+ if (this.isExecutableIntent(intentResolution)) {
7049
+ return null;
7050
+ }
5864
7051
  const message = intentResolution.assistantMessage?.trim();
5865
7052
  return message || null;
5866
7053
  }
7054
+ isExecutableIntent(intentResolution) {
7055
+ const operationKind = intentResolution.operationKind;
7056
+ const artifactKind = intentResolution.artifactKind;
7057
+ return !!intentResolution.valid
7058
+ && intentResolution.gate?.status === 'eligible'
7059
+ && !!intentResolution.selectedCandidate
7060
+ && (artifactKind === 'form' || artifactKind === 'dashboard' || artifactKind === 'table' || artifactKind === 'page')
7061
+ && (operationKind === 'create' || operationKind === 'modify' || operationKind === 'remove' || operationKind === 'compose');
7062
+ }
5867
7063
  resolveIntentClarification(intentResolution) {
5868
7064
  const questions = intentResolution.clarificationQuestions?.filter((question) => !!question) ?? [];
5869
7065
  if (!intentResolution.valid && questions.length) {
@@ -5872,7 +7068,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
5872
7068
  return null;
5873
7069
  }
5874
7070
  describeIntentResolutionFailure(intentResolution) {
5875
- if (intentResolution.valid && intentResolution.gate?.status === 'eligible' && intentResolution.selectedCandidate) {
7071
+ if (this.isExecutableIntent(intentResolution)) {
5876
7072
  return null;
5877
7073
  }
5878
7074
  const questions = intentResolution.clarificationQuestions?.filter((question) => !!question) ?? [];
@@ -5897,6 +7093,10 @@ class PageBuilderAgenticAuthoringTurnFlow {
5897
7093
  kind: reply.kind,
5898
7094
  label: reply.label,
5899
7095
  prompt: reply.prompt,
7096
+ description: reply.description,
7097
+ icon: reply.icon,
7098
+ tone: reply.tone,
7099
+ contextHints: this.toJsonObject(reply.contextHints),
5900
7100
  }));
5901
7101
  }
5902
7102
  buildPendingClarificationForNextTurn(request, sourcePrompt, clarification) {
@@ -5947,16 +7147,47 @@ class PageBuilderAgenticAuthoringTurnFlow {
5947
7147
  diagnostics: this.toJsonObject(pending?.diagnostics) ?? undefined,
5948
7148
  };
5949
7149
  }
7150
+ hasPendingClarificationQuestion(pending) {
7151
+ return !!pending?.sourcePrompt?.trim() && pending.questions.some((question) => !!question.label?.trim());
7152
+ }
5950
7153
  resolveEffectivePrompt(intentResolution, fallbackPrompt) {
5951
7154
  return intentResolution.effectivePrompt?.trim() || fallbackPrompt;
5952
7155
  }
5953
7156
  buildAuthoringContext(request) {
7157
+ const contextHints = this.buildContextHints(request);
5954
7158
  return {
5955
7159
  sessionId: request.sessionId,
5956
7160
  clientTurnId: request.clientTurnId,
5957
7161
  conversationMessages: this.toConversationMessages(request.messages ?? []),
5958
7162
  pendingClarification: this.toPendingClarification(request.pendingClarification),
5959
7163
  attachmentSummaries: this.toAttachmentSummaries(request.attachments ?? []),
7164
+ contextHints,
7165
+ };
7166
+ }
7167
+ buildIntentResolutionRequest(prompt, selectedWidgetKey, componentCapabilities, authoringContext) {
7168
+ return {
7169
+ userPrompt: prompt,
7170
+ targetApp: this.context.targetApp,
7171
+ targetComponentId: this.context.targetComponentId,
7172
+ currentPage: this.context.currentPage(),
7173
+ selectedWidgetKey,
7174
+ provider: this.context.provider(),
7175
+ model: this.context.model(),
7176
+ apiKey: this.context.apiKey(),
7177
+ componentCapabilities,
7178
+ ...authoringContext,
7179
+ };
7180
+ }
7181
+ buildContextHints(request) {
7182
+ const base = this.toJsonObject(request.action?.contextHints)
7183
+ ?? this.toJsonObject(request.contextHints);
7184
+ const includeLlmDiagnostics = this.context.includeLlmDiagnostics?.() === true;
7185
+ if (!includeLlmDiagnostics) {
7186
+ return base ?? undefined;
7187
+ }
7188
+ return {
7189
+ ...(base ?? {}),
7190
+ includeLlmDiagnostics: true,
5960
7191
  };
5961
7192
  }
5962
7193
  toAttachmentSummaries(attachments) {
@@ -6005,6 +7236,14 @@ class PageBuilderAgenticAuthoringTurnFlow {
6005
7236
  ? value
6006
7237
  : null;
6007
7238
  }
7239
+ readString(value, key) {
7240
+ const raw = value?.[key];
7241
+ return typeof raw === 'string' ? raw.trim() : '';
7242
+ }
7243
+ readNumber(value, key) {
7244
+ const raw = value?.[key];
7245
+ return typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
7246
+ }
6008
7247
  }
6009
7248
 
6010
7249
  function buildConnectionsViewerModel(page) {
@@ -6017,6 +7256,12 @@ function buildConnectionsViewerModel(page) {
6017
7256
  const edges = links.map((link) => {
6018
7257
  const fromWidgetId = link.from.kind === 'component-port' ? link.from.ref.widget : null;
6019
7258
  const toWidgetId = link.to.kind === 'component-port' ? link.to.ref.widget : null;
7259
+ const fromNestedPath = link.from.kind === 'component-port' && link.from.ref.nestedPath?.length
7260
+ ? clone(link.from.ref.nestedPath)
7261
+ : null;
7262
+ const toNestedPath = link.to.kind === 'component-port' && link.to.ref.nestedPath?.length
7263
+ ? clone(link.to.ref.nestedPath)
7264
+ : null;
6020
7265
  const toStatePath = link.to.kind === 'state' ? link.to.ref.path : null;
6021
7266
  const diagnosticReasons = [];
6022
7267
  if (fromWidgetId && !counts.has(fromWidgetId)) {
@@ -6039,9 +7284,17 @@ function buildConnectionsViewerModel(page) {
6039
7284
  id: link.id,
6040
7285
  fromKind: link.from.kind,
6041
7286
  fromWidgetId,
7287
+ fromEndpointId: link.from.kind === 'component-port'
7288
+ ? buildComponentEndpointId(link.from.ref.widget, link.from.ref.nestedPath)
7289
+ : null,
7290
+ fromNestedPath,
6042
7291
  fromPort: link.from.kind === 'component-port' ? link.from.ref.port : null,
6043
7292
  toKind: link.to.kind,
6044
7293
  toWidgetId,
7294
+ toEndpointId: link.to.kind === 'component-port'
7295
+ ? buildComponentEndpointId(link.to.ref.widget, link.to.ref.nestedPath)
7296
+ : null,
7297
+ toNestedPath,
6045
7298
  toPort: link.to.kind === 'component-port' ? link.to.ref.port : null,
6046
7299
  toStatePath,
6047
7300
  intent: link.intent,
@@ -6072,6 +7325,31 @@ function buildConnectionsViewerModel(page) {
6072
7325
  diagnosticLinks: edges.filter((edge) => edge.hasDiagnostics).length,
6073
7326
  };
6074
7327
  }
7328
+ function buildComponentEndpointId(ownerWidgetKey, nestedPath) {
7329
+ if (!nestedPath?.length) {
7330
+ return ownerWidgetKey;
7331
+ }
7332
+ return `${ownerWidgetKey}${nestedPath
7333
+ .map((segment) => `/${formatPathSegment(segment)}`)
7334
+ .join('')}`;
7335
+ }
7336
+ function formatPathSegment(segment) {
7337
+ if (segment.kind === 'widget') {
7338
+ const identity = segment.key
7339
+ || segment.componentType
7340
+ || (typeof segment.index === 'number' ? String(segment.index) : 'unknown');
7341
+ return `widget:${identity}`;
7342
+ }
7343
+ return [
7344
+ segment.kind,
7345
+ segment.id || segment.key || segment.index,
7346
+ ].filter((part) => part !== undefined && part !== '').join(':');
7347
+ }
7348
+ function clone(value) {
7349
+ if (value == null || typeof value !== 'object')
7350
+ return value;
7351
+ return JSON.parse(JSON.stringify(value));
7352
+ }
6075
7353
 
6076
7354
  class ConnectionsViewerPanelComponent {
6077
7355
  i18n = inject(PraxisI18nService);
@@ -6522,6 +7800,8 @@ class DynamicPageBuilderComponent {
6522
7800
  agenticAuthoringComponentId;
6523
7801
  agenticAuthoringScope = 'user';
6524
7802
  agenticAuthoringEtag;
7803
+ agenticAuthoringIncludeLlmDiagnostics = false;
7804
+ agenticAuthoringEnableStreaming = false;
6525
7805
  pageChange = new EventEmitter();
6526
7806
  agenticAuthoringApplied = new EventEmitter();
6527
7807
  currentPage = signal({ widgets: [] }, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
@@ -6536,6 +7816,7 @@ class DynamicPageBuilderComponent {
6536
7816
  agenticAuthoringConversation = signal([], ...(ngDevMode ? [{ debugName: "agenticAuthoringConversation" }] : []));
6537
7817
  agenticAuthoringQuickReplies = signal([], ...(ngDevMode ? [{ debugName: "agenticAuthoringQuickReplies" }] : []));
6538
7818
  agenticAuthoringAttachments = signal([], ...(ngDevMode ? [{ debugName: "agenticAuthoringAttachments" }] : []));
7819
+ agenticAuthoringLlmDiagnostics = signal(null, ...(ngDevMode ? [{ debugName: "agenticAuthoringLlmDiagnostics" }] : []));
6539
7820
  agenticAuthoringEditingMessageId = signal(null, ...(ngDevMode ? [{ debugName: "agenticAuthoringEditingMessageId" }] : []));
6540
7821
  agenticAuthoringPanelLayout = signal({
6541
7822
  left: 16,
@@ -6568,6 +7849,7 @@ class DynamicPageBuilderComponent {
6568
7849
  this.previewMode = !this.previewMode;
6569
7850
  if (this.previewMode) {
6570
7851
  this.connectionsViewerOpen.set(false);
7852
+ this.agenticAuthoringLlmDiagnostics.set(null);
6571
7853
  }
6572
7854
  }
6573
7855
  toggleConnectionsViewer() {
@@ -6728,11 +8010,10 @@ class DynamicPageBuilderComponent {
6728
8010
  try {
6729
8011
  const controller = this.ensureAgenticTurnController();
6730
8012
  const editingMessageId = this.agenticAuthoringEditingMessageId();
6731
- const state = await firstValueFrom(editingMessageId
8013
+ await this.consumeAgenticTurn(editingMessageId
6732
8014
  ? controller.submitEditedMessage(editingMessageId, prompt)
6733
8015
  : controller.submitPrompt(prompt));
6734
8016
  this.agenticAuthoringEditingMessageId.set(null);
6735
- this.applyAgenticTurnState(state);
6736
8017
  }
6737
8018
  catch (error) {
6738
8019
  this.agenticAuthoringStatus.set('');
@@ -6745,24 +8026,79 @@ class DynamicPageBuilderComponent {
6745
8026
  }
6746
8027
  }
6747
8028
  async submitAgenticQuickReply(reply) {
6748
- if (reply.kind !== 'cancel' && reply.kind !== 'revise' && reply.kind !== 'confirm' && reply.kind !== 'suggestion') {
6749
- return;
6750
- }
6751
- if (reply.kind === 'cancel') {
8029
+ const replyKind = (reply.kind || 'suggestion').trim().toLowerCase();
8030
+ if (replyKind === 'cancel') {
6752
8031
  this.agenticAuthoringEditingMessageId.set(null);
6753
- const state = await firstValueFrom(this.ensureAgenticTurnController().cancel());
6754
- this.applyAgenticTurnState(state);
8032
+ await this.consumeAgenticTurn(this.ensureAgenticTurnController().cancel());
6755
8033
  return;
6756
8034
  }
6757
- if (reply.kind === 'revise') {
8035
+ if (replyKind === 'revise') {
6758
8036
  this.agenticAuthoringEditingMessageId.set(null);
6759
8037
  this.agenticAuthoringQuickReplies.set([]);
6760
8038
  this.agenticAuthoringStatus.set(this.tx('agentic.status.waitingRevision', 'Refine your prompt and preview again.'));
6761
8039
  return;
6762
8040
  }
6763
8041
  this.agenticAuthoringEditingMessageId.set(null);
6764
- this.agenticAuthoringPrompt.set(reply.prompt);
6765
- await this.previewAgenticAuthoring();
8042
+ const contextHints = reply.contextHints ? { ...reply.contextHints } : undefined;
8043
+ const visiblePrompt = this.agenticQuickReplyVisiblePrompt(reply, contextHints);
8044
+ this.agenticAuthoringPrompt.set(visiblePrompt);
8045
+ this.agenticAuthoringBusy.set(true);
8046
+ this.agenticAuthoringError.set('');
8047
+ this.agenticAuthoringPreviewResult.set(null);
8048
+ this.agenticAuthoringStatus.set(this.tx('agentic.status.resolvingIntent', 'Resolving intent...'));
8049
+ try {
8050
+ const controller = this.ensureAgenticTurnController();
8051
+ await this.consumeAgenticTurn(controller.snapshot().state === 'clarification'
8052
+ ? controller.answerClarification({
8053
+ id: reply.id,
8054
+ label: visiblePrompt,
8055
+ value: reply.prompt,
8056
+ description: reply.description ?? undefined,
8057
+ contextHints,
8058
+ })
8059
+ : controller.submitPrompt(visiblePrompt, {
8060
+ kind: replyKind || 'quick-reply',
8061
+ id: reply.id,
8062
+ value: reply.prompt,
8063
+ contextHints,
8064
+ }));
8065
+ }
8066
+ catch (error) {
8067
+ this.agenticAuthoringStatus.set('');
8068
+ const message = this.describeAgenticError(error);
8069
+ this.agenticAuthoringError.set(message);
8070
+ this.appendAgenticMessage('error', message);
8071
+ }
8072
+ finally {
8073
+ this.agenticAuthoringBusy.set(false);
8074
+ }
8075
+ }
8076
+ agenticQuickReplyVisiblePrompt(reply, contextHints) {
8077
+ if (!this.isResourceQuickReply(reply, contextHints)) {
8078
+ return reply.prompt;
8079
+ }
8080
+ const label = (reply.label || this.describeResourceContext(contextHints) || reply.prompt).trim();
8081
+ const resourcePath = String(contextHints?.['resourcePath'] || '').trim();
8082
+ return this.tx('agentic.quickReplies.resourceSelectionPrompt', 'Use {label} ({resourcePath}) as the data source.')
8083
+ .replace('{label}', label)
8084
+ .replace('{resourcePath}', resourcePath);
8085
+ }
8086
+ isResourceQuickReply(reply, contextHints) {
8087
+ return reply.kind === 'suggestion'
8088
+ && typeof contextHints?.['resourcePath'] === 'string'
8089
+ && !!String(contextHints['resourcePath']).trim();
8090
+ }
8091
+ describeResourceContext(contextHints) {
8092
+ const resourcePath = typeof contextHints?.['resourcePath'] === 'string'
8093
+ ? contextHints['resourcePath'].trim()
8094
+ : '';
8095
+ if (!resourcePath)
8096
+ return '';
8097
+ const segment = resourcePath.split('/').filter((part) => !!part).pop() || resourcePath;
8098
+ return segment
8099
+ .replace(/[-_]+/g, ' ')
8100
+ .replace(/\s+/g, ' ')
8101
+ .trim();
6766
8102
  }
6767
8103
  attachAgenticContext() {
6768
8104
  const controller = this.ensureAgenticTurnController();
@@ -6807,8 +8143,7 @@ class DynamicPageBuilderComponent {
6807
8143
  this.agenticAuthoringError.set('');
6808
8144
  this.agenticAuthoringPreviewResult.set(null);
6809
8145
  try {
6810
- const state = await firstValueFrom(this.ensureAgenticTurnController().resendMessage(message.id));
6811
- this.applyAgenticTurnState(state);
8146
+ await this.consumeAgenticTurn(this.ensureAgenticTurnController().resendMessage(message.id));
6812
8147
  }
6813
8148
  catch (error) {
6814
8149
  this.agenticAuthoringStatus.set('');
@@ -6877,6 +8212,8 @@ class DynamicPageBuilderComponent {
6877
8212
  provider: () => this.agenticAuthoringProvider,
6878
8213
  model: () => this.agenticAuthoringModel,
6879
8214
  apiKey: () => this.agenticAuthoringApiKey,
8215
+ enableTurnStream: () => this.agenticAuthoringEnableStreaming,
8216
+ includeLlmDiagnostics: () => this.agenticAuthoringIncludeLlmDiagnostics,
6880
8217
  loadComponentCapabilities: () => this.loadAgenticComponentCapabilities(),
6881
8218
  applyLocalPreview: (result) => this.applyAgenticPreviewLocally(result),
6882
8219
  describePreviewFailure: (result) => this.describeAgenticPreviewFailure(result),
@@ -6898,20 +8235,55 @@ class DynamicPageBuilderComponent {
6898
8235
  }
6899
8236
  async applyAgenticPreviewLocally(result) {
6900
8237
  const adapter = new PageBuilderAiAdapter(this.createAdapterHost(), this.componentMetadata);
6901
- return result.uiCompositionPlan
8238
+ const applied = await (result.uiCompositionPlan
6902
8239
  ? adapter.applyUiCompositionPlan(result.uiCompositionPlan)
6903
- : adapter.applyCompiledFormPatch(result.compiledFormPatch);
8240
+ : adapter.applyCompiledFormPatch(result.compiledFormPatch));
8241
+ if (!applied.success && this.isAgenticTableContractError(applied.error)) {
8242
+ return {
8243
+ ...applied,
8244
+ error: this.tx('agentic.errors.invalidTableContract', 'I found the data source, but the generated plan used properties that are incompatible with the table component. I will adjust it to use only supported fields.'),
8245
+ };
8246
+ }
8247
+ return applied;
6904
8248
  }
6905
8249
  applyAgenticTurnState(state) {
8250
+ const preview = state.preview ?? null;
6906
8251
  this.agenticAuthoringConversation.set(state.messages);
6907
- this.agenticAuthoringQuickReplies.set(state.quickReplies);
8252
+ this.agenticAuthoringQuickReplies.set(preview?.valid ? [] : state.quickReplies);
6908
8253
  this.agenticAuthoringStatus.set(state.statusText);
6909
8254
  this.agenticAuthoringError.set(state.errorText);
6910
- this.agenticAuthoringPreviewResult.set(state.preview ?? null);
8255
+ this.agenticAuthoringPreviewResult.set(preview);
6911
8256
  this.agenticAuthoringAttachments.set(state.attachments);
8257
+ this.agenticAuthoringLlmDiagnostics.set(this.resolveAgenticLlmDiagnostics(state.diagnostics));
8258
+ }
8259
+ consumeAgenticTurn(states$) {
8260
+ return lastValueFrom(states$.pipe(tap((state) => this.applyAgenticTurnState(state))));
8261
+ }
8262
+ agenticAuthoringDiagnosticsTop() {
8263
+ return Math.max(16, this.agenticAuthoringPanelLayout().top);
8264
+ }
8265
+ agenticAuthoringLlmDiagnosticsText() {
8266
+ const diagnostics = this.agenticAuthoringLlmDiagnostics();
8267
+ if (!this.agenticAuthoringIncludeLlmDiagnostics || !diagnostics) {
8268
+ return '';
8269
+ }
8270
+ return JSON.stringify(diagnostics, null, 2);
8271
+ }
8272
+ resolveAgenticLlmDiagnostics(diagnostics) {
8273
+ if (!this.agenticAuthoringIncludeLlmDiagnostics) {
8274
+ return null;
8275
+ }
8276
+ const intentResolution = this.toRecord(diagnostics?.['intentResolution']);
8277
+ return this.toRecord(intentResolution?.['llmDiagnostics']);
8278
+ }
8279
+ toRecord(value) {
8280
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
8281
+ return null;
8282
+ }
8283
+ return value;
6912
8284
  }
6913
8285
  resolvePreviewCompiledFormPatch(preview) {
6914
- if (preview.uiCompositionPlan) {
8286
+ if (preview.uiCompositionPlan || preview.compiledFormPatch?.patch?.page) {
6915
8287
  return {
6916
8288
  ...preview.compiledFormPatch,
6917
8289
  patch: {
@@ -6920,9 +8292,6 @@ class DynamicPageBuilderComponent {
6920
8292
  },
6921
8293
  };
6922
8294
  }
6923
- if (preview.compiledFormPatch?.patch?.page) {
6924
- return preview.compiledFormPatch;
6925
- }
6926
8295
  return preview.compiledFormPatch;
6927
8296
  }
6928
8297
  focusCanvasWidget(widgetKey) {
@@ -6994,6 +8363,12 @@ class DynamicPageBuilderComponent {
6994
8363
  }
6995
8364
  }
6996
8365
  describeAgenticPreviewStatus(result) {
8366
+ const assistantMessage = typeof result.assistantMessage === 'string'
8367
+ ? result.assistantMessage.trim()
8368
+ : '';
8369
+ if (assistantMessage) {
8370
+ return assistantMessage;
8371
+ }
6997
8372
  switch (result.diagnostics?.fieldScopeDecision) {
6998
8373
  case 'accepted-add-local-field':
6999
8374
  return this.tx('agentic.status.acceptedAddLocalField', 'Local field added to the form.');
@@ -7005,6 +8380,11 @@ class DynamicPageBuilderComponent {
7005
8380
  return this.tx('agentic.status.previewReady', 'Preview applied to the page.');
7006
8381
  }
7007
8382
  }
8383
+ isAgenticTableContractError(error) {
8384
+ const message = typeof error === 'string' ? error.toLowerCase() : '';
8385
+ return message.includes('praxis-table')
8386
+ && (message.includes('unknown input') || message.includes('unknown inputs') || message.includes('unsupported'));
8387
+ }
7008
8388
  appendAgenticMessage(role, text) {
7009
8389
  const normalized = text.trim();
7010
8390
  if (!normalized)
@@ -7043,7 +8423,7 @@ class DynamicPageBuilderComponent {
7043
8423
  return resolvePraxisPageBuilderText(this.i18n, key, fallback);
7044
8424
  }
7045
8425
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicPageBuilderComponent, deps: [{ token: i1.MatDialog }, { token: SETTINGS_PANEL_BRIDGE, optional: true }], target: i0.ɵɵFactoryTarget.Component });
7046
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: DynamicPageBuilderComponent, isStandalone: true, selector: "praxis-dynamic-page-builder", inputs: { page: "page", context: "context", strictValidation: "strictValidation", autoPersist: "autoPersist", enableCustomization: "enableCustomization", showSettingsButton: "showSettingsButton", pageIdentity: "pageIdentity", componentInstanceId: "componentInstanceId", pageEditorComponent: "pageEditorComponent", enableAgenticAuthoring: "enableAgenticAuthoring", agenticAuthoringProvider: "agenticAuthoringProvider", agenticAuthoringModel: "agenticAuthoringModel", agenticAuthoringApiKey: "agenticAuthoringApiKey", agenticAuthoringComponentId: "agenticAuthoringComponentId", agenticAuthoringScope: "agenticAuthoringScope", agenticAuthoringEtag: "agenticAuthoringEtag" }, outputs: { pageChange: "pageChange", agenticAuthoringApplied: "agenticAuthoringApplied" }, providers: [
8426
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: DynamicPageBuilderComponent, isStandalone: true, selector: "praxis-dynamic-page-builder", inputs: { page: "page", context: "context", strictValidation: "strictValidation", autoPersist: "autoPersist", enableCustomization: "enableCustomization", showSettingsButton: "showSettingsButton", pageIdentity: "pageIdentity", componentInstanceId: "componentInstanceId", pageEditorComponent: "pageEditorComponent", enableAgenticAuthoring: "enableAgenticAuthoring", agenticAuthoringProvider: "agenticAuthoringProvider", agenticAuthoringModel: "agenticAuthoringModel", agenticAuthoringApiKey: "agenticAuthoringApiKey", agenticAuthoringComponentId: "agenticAuthoringComponentId", agenticAuthoringScope: "agenticAuthoringScope", agenticAuthoringEtag: "agenticAuthoringEtag", agenticAuthoringIncludeLlmDiagnostics: "agenticAuthoringIncludeLlmDiagnostics", agenticAuthoringEnableStreaming: "agenticAuthoringEnableStreaming" }, outputs: { pageChange: "pageChange", agenticAuthoringApplied: "agenticAuthoringApplied" }, providers: [
7047
8427
  providePraxisPageBuilderI18n(),
7048
8428
  { provide: DYNAMIC_PAGE_SHELL_EDITOR, useValue: WidgetShellEditorComponent },
7049
8429
  ], viewQueries: [{ propertyName: "runtime", first: true, predicate: ["runtime"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -7107,6 +8487,26 @@ class DynamicPageBuilderComponent {
7107
8487
  (close)="toggleAgenticAuthoring()"
7108
8488
  />
7109
8489
 
8490
+ <section
8491
+ *ngIf="agenticAuthoringIncludeLlmDiagnostics && agenticAuthoringLlmDiagnosticsText()"
8492
+ class="agentic-diagnostics-panel"
8493
+ data-testid="page-builder-agentic-llm-diagnostics"
8494
+ role="region"
8495
+ [attr.aria-label]="tx('agentic.diagnostics.title', 'LLM diagnostics')"
8496
+ [style.top.px]="agenticAuthoringDiagnosticsTop()"
8497
+ >
8498
+ <header class="agentic-diagnostics-panel__header">
8499
+ <span>{{ tx('agentic.diagnostics.title', 'LLM diagnostics') }}</span>
8500
+ <span class="agentic-diagnostics-panel__badge">
8501
+ {{ tx('agentic.diagnostics.badge', 'Debug') }}
8502
+ </span>
8503
+ </header>
8504
+ <p class="agentic-diagnostics-panel__description">
8505
+ {{ tx('agentic.diagnostics.description', 'Prompt, context bundle, and tool catalog returned by the backend for this turn.') }}
8506
+ </p>
8507
+ <pre>{{ agenticAuthoringLlmDiagnosticsText() }}</pre>
8508
+ </section>
8509
+
7110
8510
  <praxis-floating-toolbar
7111
8511
  [visible]="showSettings()"
7112
8512
  [canUndo]="false"
@@ -7141,7 +8541,7 @@ class DynamicPageBuilderComponent {
7141
8541
  </button>
7142
8542
  </praxis-floating-toolbar>
7143
8543
  </div>
7144
- `, isInline: true, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: DynamicWidgetPageComponent, selector: "praxis-dynamic-page", inputs: ["page", "context", "strictValidation", "enableCustomization", "showPageSettingsButton", "shellEditorComponent", "pageEditorComponent", "autoPersist", "pageIdentity", "componentInstanceId"], outputs: ["pageChange", "widgetEvent", "widgetDiagnosticsChange"] }, { kind: "component", type: FloatingToolbarComponent, selector: "praxis-floating-toolbar", inputs: ["visible", "canUndo", "canRedo"], outputs: ["add", "undo", "redo", "settings", "preview", "save"] }, { kind: "component", type: ConnectionsViewerPanelComponent, selector: "praxis-connections-viewer-panel", inputs: ["open", "page"], outputs: ["focusWidget", "openPageSettings"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "busy", "canSubmit", "canApply", "submitOnEnter", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }] });
8544
+ `, isInline: true, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%}.agentic-diagnostics-panel{position:absolute;z-index:21;right:16px;width:min(520px,calc(100% - 32px));max-height:min(440px,calc(100% - 32px));overflow:auto;padding:12px;border:1px solid rgba(107,114,128,.28);border-radius:8px;background:#fffffff5;color:#111827;box-shadow:0 14px 36px #0000003d}.agentic-diagnostics-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700}.agentic-diagnostics-panel__badge{padding:2px 6px;border:1px solid rgba(107,114,128,.28);border-radius:8px;font-size:11px;font-weight:600;color:#4b5563}.agentic-diagnostics-panel__description{margin:8px 0 10px;color:#4b5563;font-size:12px;line-height:1.4}.agentic-diagnostics-panel pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.45;color:#1f2937}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: DynamicWidgetPageComponent, selector: "praxis-dynamic-page", inputs: ["page", "context", "strictValidation", "enableCustomization", "showPageSettingsButton", "shellEditorComponent", "pageEditorComponent", "autoPersist", "pageIdentity", "componentInstanceId"], outputs: ["pageChange", "widgetEvent", "widgetDiagnosticsChange"] }, { kind: "component", type: FloatingToolbarComponent, selector: "praxis-floating-toolbar", inputs: ["visible", "canUndo", "canRedo"], outputs: ["add", "undo", "redo", "settings", "preview", "save"] }, { kind: "component", type: ConnectionsViewerPanelComponent, selector: "praxis-connections-viewer-panel", inputs: ["open", "page"], outputs: ["focusWidget", "openPageSettings"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "busy", "canSubmit", "canApply", "submitOnEnter", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }] });
7145
8545
  }
7146
8546
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicPageBuilderComponent, decorators: [{
7147
8547
  type: Component,
@@ -7219,6 +8619,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7219
8619
  (close)="toggleAgenticAuthoring()"
7220
8620
  />
7221
8621
 
8622
+ <section
8623
+ *ngIf="agenticAuthoringIncludeLlmDiagnostics && agenticAuthoringLlmDiagnosticsText()"
8624
+ class="agentic-diagnostics-panel"
8625
+ data-testid="page-builder-agentic-llm-diagnostics"
8626
+ role="region"
8627
+ [attr.aria-label]="tx('agentic.diagnostics.title', 'LLM diagnostics')"
8628
+ [style.top.px]="agenticAuthoringDiagnosticsTop()"
8629
+ >
8630
+ <header class="agentic-diagnostics-panel__header">
8631
+ <span>{{ tx('agentic.diagnostics.title', 'LLM diagnostics') }}</span>
8632
+ <span class="agentic-diagnostics-panel__badge">
8633
+ {{ tx('agentic.diagnostics.badge', 'Debug') }}
8634
+ </span>
8635
+ </header>
8636
+ <p class="agentic-diagnostics-panel__description">
8637
+ {{ tx('agentic.diagnostics.description', 'Prompt, context bundle, and tool catalog returned by the backend for this turn.') }}
8638
+ </p>
8639
+ <pre>{{ agenticAuthoringLlmDiagnosticsText() }}</pre>
8640
+ </section>
8641
+
7222
8642
  <praxis-floating-toolbar
7223
8643
  [visible]="showSettings()"
7224
8644
  [canUndo]="false"
@@ -7253,7 +8673,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7253
8673
  </button>
7254
8674
  </praxis-floating-toolbar>
7255
8675
  </div>
7256
- `, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%}\n"] }]
8676
+ `, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%}.agentic-diagnostics-panel{position:absolute;z-index:21;right:16px;width:min(520px,calc(100% - 32px));max-height:min(440px,calc(100% - 32px));overflow:auto;padding:12px;border:1px solid rgba(107,114,128,.28);border-radius:8px;background:#fffffff5;color:#111827;box-shadow:0 14px 36px #0000003d}.agentic-diagnostics-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700}.agentic-diagnostics-panel__badge{padding:2px 6px;border:1px solid rgba(107,114,128,.28);border-radius:8px;font-size:11px;font-weight:600;color:#4b5563}.agentic-diagnostics-panel__description{margin:8px 0 10px;color:#4b5563;font-size:12px;line-height:1.4}.agentic-diagnostics-panel pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.45;color:#1f2937}\n"] }]
7257
8677
  }], ctorParameters: () => [{ type: i1.MatDialog }, { type: undefined, decorators: [{
7258
8678
  type: Optional
7259
8679
  }, {
@@ -7294,6 +8714,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7294
8714
  type: Input
7295
8715
  }], agenticAuthoringEtag: [{
7296
8716
  type: Input
8717
+ }], agenticAuthoringIncludeLlmDiagnostics: [{
8718
+ type: Input
8719
+ }], agenticAuthoringEnableStreaming: [{
8720
+ type: Input
7297
8721
  }], pageChange: [{
7298
8722
  type: Output
7299
8723
  }], agenticAuthoringApplied: [{
@@ -7309,4 +8733,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7309
8733
  * Generated bundle index. Do not edit.
7310
8734
  */
7311
8735
 
7312
- export { ComponentPaletteDialogComponent, ConfirmDialogComponent, DynamicPageBuilderComponent, DynamicPageConfigEditorComponent, FloatingToolbarComponent, PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS, PAGE_BUILDER_AI_CAPABILITIES, PAGE_BUILDER_WIDGET_AI_CATALOGS, PLACEHOLDER, PageBuilderAgenticAuthoringService, PageConfigEditorComponent, TileToolbarComponent, WidgetShellEditorComponent, clearWidgetAiCatalogs, getPageAiCatalog, getWidgetAiCapabilities, providePageBuilderWidgetAiCatalogs, registerWidgetAiCatalog, registerWidgetAiCatalogs };
8736
+ export { ComponentPaletteDialogComponent, ConfirmDialogComponent, DynamicPageBuilderComponent, DynamicPageConfigEditorComponent, FloatingToolbarComponent, PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS, PAGE_BUILDER_AI_CAPABILITIES, PAGE_BUILDER_WIDGET_AI_CATALOGS, PLACEHOLDER, PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST, PageBuilderAgenticAuthoringService, PageConfigEditorComponent, TileToolbarComponent, WidgetShellEditorComponent, clearWidgetAiCatalogs, getPageAiCatalog, getWidgetAiCapabilities, providePageBuilderWidgetAiCatalogs, registerWidgetAiCatalog, registerWidgetAiCatalogs };