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

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,8 @@ 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
+ '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
4815
  'Payloads legados de layout ainda podem ser ingeridos, mas sao normalizados para canvas e nao devem ser reemitidos.',
4782
4816
  'pageIdentity identifica o escopo de persistencia e nao pertence ao objeto page.',
4783
4817
  ],
@@ -4796,6 +4830,18 @@ const PAGE_BUILDER_AI_CAPABILITIES = {
4796
4830
  { path: 'uiCompositionPlan.bindings[].id', category: 'connections', valueKind: 'string', description: 'Identificador estavel do binding planejado.' },
4797
4831
  { path: 'uiCompositionPlan.bindings[].from', category: 'connections', valueKind: 'object', description: 'Endpoint de origem: component-port ou state.' },
4798
4832
  { path: 'uiCompositionPlan.bindings[].to', category: 'connections', valueKind: 'object', description: 'Endpoint de destino: component-port ou state.' },
4833
+ { 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.' },
4834
+ { 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.' },
4835
+ { 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.' },
4836
+ { path: 'uiCompositionPlan.bindings[].from.nestedPath[].id', category: 'connections', valueKind: 'string', description: 'Identificador estrutural estavel do container de origem no segmento nested, quando existir.' },
4837
+ { 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 },
4838
+ { 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.' },
4839
+ { 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.' },
4840
+ { 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.' },
4841
+ { path: 'uiCompositionPlan.bindings[].to.nestedPath[].id', category: 'connections', valueKind: 'string', description: 'Identificador estrutural estavel do container de destino no segmento nested, quando existir.' },
4842
+ { 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 },
4843
+ { 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.' },
4844
+ { 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
4845
  { path: 'uiCompositionPlan.bindings[].intent', category: 'connections', valueKind: 'string', description: 'Intencao semantica do binding.' },
4800
4846
  { path: 'uiCompositionPlan.bindings[].condition', category: 'connections', valueKind: 'expression', description: 'Guarda opcional em Json Logic.' },
4801
4847
  { path: 'uiCompositionPlan.bindings[].transform', category: 'connections', valueKind: 'object', description: 'Transform declarativo: pick-path, query-context, template ou constant.' },
@@ -4847,6 +4893,10 @@ const PAGE_BUILDER_AI_CAPABILITIES = {
4847
4893
  { path: 'page.composition.links[].id', category: 'connections', valueKind: 'string', description: 'Identificador estavel do link.' },
4848
4894
  { path: 'page.composition.links[].from', category: 'connections', valueKind: 'object', description: 'Endpoint de origem do link.' },
4849
4895
  { path: 'page.composition.links[].to', category: 'connections', valueKind: 'object', description: 'Endpoint de destino do link.' },
4896
+ { 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: [].' },
4897
+ { 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 },
4898
+ { 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: [].' },
4899
+ { 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
4900
  { path: 'page.composition.links[].intent', category: 'connections', valueKind: 'string', description: 'Intencao semantica do link.' },
4851
4901
  { path: 'page.composition.links[].transform', category: 'connections', valueKind: 'object', description: 'Pipeline de transformacao do link.' },
4852
4902
  { 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 +5076,12 @@ class PageBuilderAgenticAuthoringService {
5026
5076
  resolveIntent(request) {
5027
5077
  return this.http.post(`${this.baseUrl}/intent-resolution`, request, { headers: this.buildHeaders() });
5028
5078
  }
5079
+ searchResourceCandidates(request) {
5080
+ return this.http.post(`${this.baseUrl}/resource-candidates`, request, { headers: this.buildHeaders() });
5081
+ }
5082
+ streamTurn(request) {
5083
+ return this.http.post(`${this.baseUrl}/turn/stream/start`, request, { headers: this.buildHeaders() }).pipe(switchMap((start) => this.connectTurnStream(start)));
5084
+ }
5029
5085
  applyPage(request) {
5030
5086
  const { ifMatch, ...body } = request;
5031
5087
  return this.http.post(`${this.baseUrl}/page-apply`, {
@@ -5055,6 +5111,97 @@ class PageBuilderAgenticAuthoringService {
5055
5111
  }
5056
5112
  return new HttpHeaders(merged);
5057
5113
  }
5114
+ connectTurnStream(start) {
5115
+ return new Observable((observer) => {
5116
+ const url = this.buildStreamUrl(start);
5117
+ let source;
5118
+ let closed = false;
5119
+ let connectionErrorTimer = null;
5120
+ const connectionErrorGraceMs = Math.max(0, this.options?.streamConnectionErrorGraceMs ?? 1500);
5121
+ try {
5122
+ source = this.createEventSource(url);
5123
+ }
5124
+ catch (error) {
5125
+ observer.error(this.streamConnectionError(error));
5126
+ return undefined;
5127
+ }
5128
+ const clearConnectionErrorTimer = () => {
5129
+ if (connectionErrorTimer) {
5130
+ clearTimeout(connectionErrorTimer);
5131
+ connectionErrorTimer = null;
5132
+ }
5133
+ };
5134
+ const closeSource = () => {
5135
+ if (closed) {
5136
+ return;
5137
+ }
5138
+ closed = true;
5139
+ clearConnectionErrorTimer();
5140
+ source.close();
5141
+ };
5142
+ const failConnection = (event) => {
5143
+ if (closed) {
5144
+ return;
5145
+ }
5146
+ observer.error(this.streamConnectionError(event));
5147
+ closeSource();
5148
+ };
5149
+ const handleMessage = (event) => {
5150
+ try {
5151
+ clearConnectionErrorTimer();
5152
+ const parsed = JSON.parse(event.data);
5153
+ observer.next(parsed);
5154
+ if (parsed.type === 'result' || parsed.type === 'error' || parsed.type === 'cancelled') {
5155
+ observer.complete();
5156
+ closeSource();
5157
+ }
5158
+ }
5159
+ catch (error) {
5160
+ observer.error(this.streamConnectionError(error));
5161
+ closeSource();
5162
+ }
5163
+ };
5164
+ source.onmessage = handleMessage;
5165
+ if (source.addEventListener) {
5166
+ for (const type of AI_STREAM_EVENT_TYPES) {
5167
+ source.addEventListener(type, handleMessage);
5168
+ }
5169
+ }
5170
+ source.onerror = (event) => {
5171
+ if (closed || connectionErrorTimer) {
5172
+ return;
5173
+ }
5174
+ if (connectionErrorGraceMs === 0) {
5175
+ failConnection(event);
5176
+ return;
5177
+ }
5178
+ connectionErrorTimer = setTimeout(() => failConnection(event), connectionErrorGraceMs);
5179
+ };
5180
+ return () => closeSource();
5181
+ });
5182
+ }
5183
+ streamConnectionError(cause) {
5184
+ return {
5185
+ praxisAgenticTurnStreamConnectionError: true,
5186
+ cause,
5187
+ };
5188
+ }
5189
+ buildStreamUrl(start) {
5190
+ const url = new URL(`${this.baseUrl}/turn/stream/${start.streamId}`, typeof window !== 'undefined' ? window.location.origin : 'http://localhost');
5191
+ if (start.streamAccessToken) {
5192
+ url.searchParams.set('accessToken', start.streamAccessToken);
5193
+ }
5194
+ return /^https?:\/\//i.test(this.baseUrl)
5195
+ ? url.toString()
5196
+ : url.pathname + url.search;
5197
+ }
5198
+ createEventSource(url) {
5199
+ const factory = this.options?.eventSourceFactory;
5200
+ if (factory) {
5201
+ return factory(url);
5202
+ }
5203
+ return new EventSource(url);
5204
+ }
5058
5205
  formatEtag(etag) {
5059
5206
  const trimmed = etag.trim();
5060
5207
  return trimmed.startsWith('"') ? trimmed : `"${trimmed}"`;
@@ -5080,8 +5227,8 @@ function compileUiCompositionPlan(plan) {
5080
5227
  valid: true,
5081
5228
  page: {
5082
5229
  layoutPreset: plan.layoutPreset,
5083
- canvas: clone(plan.canvas),
5084
- state: clone(plan.state),
5230
+ canvas: clone$1(plan.canvas),
5231
+ state: clone$1(plan.state),
5085
5232
  widgets: plan.widgets.map(toWidgetInstance),
5086
5233
  composition: {
5087
5234
  version: '1.0.0',
@@ -5176,6 +5323,7 @@ function validateEndpoint(endpoint, widgetKeys, diagnostics, path) {
5176
5323
  path: `${path}.port`,
5177
5324
  });
5178
5325
  }
5326
+ validateNestedPath(endpoint.nestedPath, diagnostics, `${path}.nestedPath`);
5179
5327
  return;
5180
5328
  }
5181
5329
  if (!endpoint.path?.trim()) {
@@ -5186,14 +5334,26 @@ function validateEndpoint(endpoint, widgetKeys, diagnostics, path) {
5186
5334
  });
5187
5335
  }
5188
5336
  }
5337
+ function validateNestedPath(nestedPath, diagnostics, path) {
5338
+ if (!nestedPath?.length)
5339
+ return;
5340
+ const terminal = nestedPath[nestedPath.length - 1];
5341
+ if (terminal?.kind !== 'widget' || !terminal.key?.trim()) {
5342
+ diagnostics.push({
5343
+ code: 'endpoint-nested-terminal-widget-key-required',
5344
+ message: 'Nested component endpoint must end with a widget segment with a stable key.',
5345
+ path,
5346
+ });
5347
+ }
5348
+ }
5189
5349
  function toWidgetInstance(widget) {
5190
5350
  return {
5191
5351
  key: widget.key,
5192
5352
  definition: {
5193
5353
  id: widget.componentId,
5194
5354
  bindingOrder: widget.bindingOrder,
5195
- inputs: clone(widget.inputs ?? {}),
5196
- outputs: clone(widget.outputs ?? {}),
5355
+ inputs: clone$1(widget.inputs ?? {}),
5356
+ outputs: clone$1(widget.outputs ?? {}),
5197
5357
  },
5198
5358
  };
5199
5359
  }
@@ -5204,10 +5364,10 @@ function toCompositionLink(binding) {
5204
5364
  from: toCompositionEndpoint(binding.from),
5205
5365
  to: toCompositionEndpoint(binding.to),
5206
5366
  intent: binding.intent,
5207
- condition: clone(binding.condition),
5367
+ condition: clone$1(binding.condition),
5208
5368
  transform,
5209
- policy: clone(binding.policy),
5210
- metadata: clone(binding.metadata),
5369
+ policy: clone$1(binding.policy),
5370
+ metadata: clone$1(binding.metadata),
5211
5371
  };
5212
5372
  }
5213
5373
  function toCompositionEndpoint(endpoint) {
@@ -5218,6 +5378,7 @@ function toCompositionEndpoint(endpoint) {
5218
5378
  widget: endpoint.widget,
5219
5379
  port: endpoint.port,
5220
5380
  direction: endpoint.direction,
5381
+ ...(endpoint.nestedPath?.length ? { nestedPath: clone$1(endpoint.nestedPath) } : {}),
5221
5382
  },
5222
5383
  };
5223
5384
  }
@@ -5265,7 +5426,7 @@ function toCompositionTransform(transform) {
5265
5426
  phase: 'link-propagation',
5266
5427
  input: { source: transform.inputSource ?? 'event' },
5267
5428
  config: {
5268
- template: clone(transform.template),
5429
+ template: clone$1(transform.template),
5269
5430
  },
5270
5431
  },
5271
5432
  ],
@@ -5282,7 +5443,7 @@ function toCompositionTransform(transform) {
5282
5443
  kind: 'constant',
5283
5444
  phase: 'link-propagation',
5284
5445
  config: {
5285
- value: clone(transform.value),
5446
+ value: clone$1(transform.value),
5286
5447
  },
5287
5448
  },
5288
5449
  ],
@@ -5305,7 +5466,7 @@ function toCompositionTransform(transform) {
5305
5466
  ],
5306
5467
  };
5307
5468
  }
5308
- function clone(value) {
5469
+ function clone$1(value) {
5309
5470
  if (value == null || typeof value !== 'object')
5310
5471
  return value;
5311
5472
  try {
@@ -5387,7 +5548,11 @@ class PageBuilderAiAdapter {
5387
5548
  error: 'CompiledFormPatch must contain patch.page.',
5388
5549
  };
5389
5550
  }
5390
- return this.applyPatch({ page }, 'agentic-authoring.compiled-form-patch');
5551
+ const materializedPage = this.clone(page);
5552
+ if (this.shouldPreserveLocalTransientFields(compiledFormPatch)) {
5553
+ this.preserveLocalTransientFields(materializedPage);
5554
+ }
5555
+ return this.applyPatch({ page: materializedPage }, 'agentic-authoring.compiled-form-patch');
5391
5556
  }
5392
5557
  async applyUiCompositionPlan(plan) {
5393
5558
  const compiled = compileUiCompositionPlan(plan);
@@ -5526,6 +5691,76 @@ class PageBuilderAiAdapter {
5526
5691
  }
5527
5692
  return merged;
5528
5693
  }
5694
+ shouldPreserveLocalTransientFields(compiledFormPatch) {
5695
+ const warnings = compiledFormPatch['warnings'];
5696
+ return Array.isArray(warnings) && warnings.includes('server-backed-field-labels-customized-locally');
5697
+ }
5698
+ preserveLocalTransientFields(patchPage) {
5699
+ const basePage = this.normalizePageForAi(this.parsePage(this.host.page));
5700
+ const baseWidgets = basePage?.widgets ?? [];
5701
+ for (const patchWidget of patchPage.widgets ?? []) {
5702
+ if (patchWidget.definition?.id !== 'praxis-dynamic-form')
5703
+ continue;
5704
+ const key = this.getWidgetKey(patchWidget);
5705
+ if (!key)
5706
+ continue;
5707
+ const baseWidget = baseWidgets.find((widget) => this.getWidgetKey(widget) === key);
5708
+ if (!baseWidget)
5709
+ continue;
5710
+ this.preserveLocalTransientFieldsForWidget(baseWidget, patchWidget);
5711
+ }
5712
+ }
5713
+ preserveLocalTransientFieldsForWidget(baseWidget, patchWidget) {
5714
+ const baseConfig = (baseWidget.definition?.inputs?.['config'] ?? {});
5715
+ const patchInputs = patchWidget.definition.inputs ??= {};
5716
+ const patchConfig = (patchInputs['config'] ??= {});
5717
+ const baseFields = Array.isArray(baseConfig['fieldMetadata']) ? baseConfig['fieldMetadata'] : [];
5718
+ const patchFields = Array.isArray(patchConfig['fieldMetadata'])
5719
+ ? patchConfig['fieldMetadata']
5720
+ : (patchConfig['fieldMetadata'] = []);
5721
+ const preservedNames = [];
5722
+ for (const field of baseFields) {
5723
+ if (!this.isLocalTransientField(field) || patchFields.some((entry) => entry?.name === field.name))
5724
+ continue;
5725
+ patchFields.push(this.clone(field));
5726
+ preservedNames.push(field.name);
5727
+ }
5728
+ if (preservedNames.length === 0)
5729
+ return;
5730
+ const baseSections = Array.isArray(baseConfig['sections']) ? baseConfig['sections'] : [];
5731
+ const patchSections = Array.isArray(patchConfig['sections'])
5732
+ ? patchConfig['sections']
5733
+ : (patchConfig['sections'] = []);
5734
+ for (const section of baseSections) {
5735
+ if (!this.sectionContainsAnyField(section, preservedNames))
5736
+ continue;
5737
+ if (this.sectionContainsAnyField({ rows: [{ columns: [{ fields: this.flattenSectionFields(patchSections) }] }] }, preservedNames)) {
5738
+ continue;
5739
+ }
5740
+ patchSections.push(this.clone(section));
5741
+ }
5742
+ }
5743
+ isLocalTransientField(field) {
5744
+ return field?.source === 'local' || field?.transient === true || field?.submitPolicy === 'omit';
5745
+ }
5746
+ sectionContainsAnyField(section, names) {
5747
+ const fields = this.flattenSectionFields([section]);
5748
+ return names.some((name) => fields.includes(name));
5749
+ }
5750
+ flattenSectionFields(sections) {
5751
+ const fields = [];
5752
+ for (const section of sections) {
5753
+ for (const row of section?.rows ?? []) {
5754
+ for (const column of row?.columns ?? []) {
5755
+ for (const field of column?.fields ?? []) {
5756
+ if (typeof field === 'string')
5757
+ fields.push(field);
5758
+ }
5759
+ }
5760
+ }
5761
+ }
5762
+ return fields;
5763
+ }
5529
5764
  mergeLinks(baseLinks, patchLinks) {
5530
5765
  const base = baseLinks || [];
5531
5766
  const patch = patchLinks || [];
@@ -5646,20 +5881,37 @@ class PageBuilderAiAdapter {
5646
5881
  linkKey(link) {
5647
5882
  if (!link)
5648
5883
  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);
5884
+ const from = this.endpointKey(link.from);
5885
+ const to = this.endpointKey(link.to);
5659
5886
  if (!from || !to)
5660
5887
  return null;
5661
5888
  return `${from}->${to}`;
5662
5889
  }
5890
+ endpointKey(endpoint) {
5891
+ if (endpoint.kind === 'component-port') {
5892
+ if (!endpoint.ref.widget || !endpoint.ref.port)
5893
+ return null;
5894
+ return [
5895
+ 'widget',
5896
+ endpoint.ref.widget,
5897
+ endpoint.ref.port,
5898
+ endpoint.ref.direction,
5899
+ this.nestedPathKey(endpoint.ref.nestedPath),
5900
+ ].filter((part) => part !== '').join('::');
5901
+ }
5902
+ return endpoint.ref.path ? `state:${endpoint.ref.path}::${endpoint.ref.layer || 'values'}` : null;
5903
+ }
5904
+ nestedPathKey(nestedPath) {
5905
+ if (!nestedPath?.length)
5906
+ return '';
5907
+ return `nested:${JSON.stringify(nestedPath.map((segment) => ({
5908
+ kind: segment.kind,
5909
+ id: segment.id,
5910
+ key: segment.key,
5911
+ index: segment.index,
5912
+ componentType: segment.componentType,
5913
+ })))}`;
5914
+ }
5663
5915
  pickLayoutOptions(options) {
5664
5916
  if (!options)
5665
5917
  return undefined;
@@ -5717,42 +5969,60 @@ class PageBuilderAgenticAuthoringTurnFlow {
5717
5969
  this.service = service;
5718
5970
  this.context = context;
5719
5971
  }
5720
- async submit(request) {
5972
+ submit(request) {
5721
5973
  const prompt = request.prompt?.trim();
5722
5974
  if (!prompt) {
5723
- return {
5975
+ return Promise.resolve({
5724
5976
  state: 'listening',
5725
5977
  phase: 'capture',
5726
- };
5978
+ });
5979
+ }
5980
+ if (this.isResourceDiscoveryTool(request)) {
5981
+ return this.handleResourceDiscoveryTool(request, prompt);
5982
+ }
5983
+ if (this.context.enableTurnStream?.() === true && this.service.streamTurn) {
5984
+ return this.submitWithTurnStream(request, prompt);
5727
5985
  }
5986
+ return this.submitSynchronously(request, prompt);
5987
+ }
5988
+ async submitSynchronously(request, prompt) {
5728
5989
  try {
5729
5990
  const authoringContext = this.buildAuthoringContext(request);
5730
5991
  const componentCapabilities = await this.context.loadComponentCapabilities();
5731
5992
  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
- }));
5993
+ const intentResolution = await firstValueFrom(this.service.resolveIntent(this.buildIntentResolutionRequest(prompt, selectedWidgetKey, componentCapabilities, authoringContext)));
5744
5994
  const intentAssistantMessage = this.resolveIntentAssistantMessage(intentResolution);
5745
5995
  if (intentAssistantMessage) {
5996
+ const quickReplies = this.toShellQuickReplies(intentResolution.quickReplies ?? []);
5997
+ const explicitPendingClarification = this.toShellPendingClarification(intentResolution.pendingClarification);
5998
+ const intentClarification = this.resolveIntentClarification(intentResolution);
5999
+ const pendingClarification = explicitPendingClarification
6000
+ ?? (intentClarification
6001
+ ? this.buildPendingClarificationForNextTurn(request, this.resolveEffectivePrompt(intentResolution, prompt), intentClarification)
6002
+ : null);
6003
+ if (!this.hasPendingClarificationQuestion(pendingClarification) && quickReplies.length === 0) {
6004
+ return {
6005
+ state: 'success',
6006
+ phase: 'summarize',
6007
+ assistantMessage: intentAssistantMessage,
6008
+ quickReplies,
6009
+ canApply: false,
6010
+ statusText: '',
6011
+ errorText: '',
6012
+ preview: null,
6013
+ diagnostics: { intentResolution },
6014
+ };
6015
+ }
5746
6016
  return {
5747
6017
  state: 'clarification',
5748
6018
  phase: 'clarify',
5749
6019
  assistantMessage: intentAssistantMessage,
5750
- quickReplies: this.toShellQuickReplies(intentResolution.quickReplies ?? []),
6020
+ quickReplies,
5751
6021
  canApply: false,
5752
6022
  statusText: '',
5753
6023
  errorText: '',
5754
6024
  preview: null,
5755
- pendingClarification: this.resolvePendingClarificationForNextTurn(intentResolution, request, this.resolveEffectivePrompt(intentResolution, prompt), intentAssistantMessage),
6025
+ pendingClarification,
5756
6026
  diagnostics: { intentResolution },
5757
6027
  };
5758
6028
  }
@@ -5827,6 +6097,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
5827
6097
  state: 'review',
5828
6098
  phase: 'review',
5829
6099
  assistantMessage: status,
6100
+ quickReplies: [],
5830
6101
  canApply: true,
5831
6102
  statusText: status,
5832
6103
  errorText: '',
@@ -5847,6 +6118,11 @@ class PageBuilderAgenticAuthoringTurnFlow {
5847
6118
  };
5848
6119
  }
5849
6120
  }
6121
+ submitWithTurnStream(request, prompt) {
6122
+ return from(this.buildTurnStreamRequest(request, prompt)).pipe(concatMap((streamRequest) => this.service.streamTurn(streamRequest)), concatMap((event) => from(this.toTurnResultFromStreamEvent(event))), catchError((error) => this.shouldFallbackFromTurnStreamError(error)
6123
+ ? from(this.submitSynchronously(request, prompt))
6124
+ : of(this.toTurnStreamTransportErrorResult(error))));
6125
+ }
5850
6126
  async cancel() {
5851
6127
  return {
5852
6128
  state: 'listening',
@@ -5860,10 +6136,413 @@ class PageBuilderAgenticAuthoringTurnFlow {
5860
6136
  pendingPatch: null,
5861
6137
  };
5862
6138
  }
6139
+ async buildTurnStreamRequest(request, prompt) {
6140
+ const componentCapabilities = await this.context.loadComponentCapabilities();
6141
+ const selectedWidgetKey = this.context.selectedWidgetKey();
6142
+ return {
6143
+ userPrompt: prompt,
6144
+ targetApp: this.context.targetApp,
6145
+ targetComponentId: this.context.targetComponentId,
6146
+ currentRoute: null,
6147
+ currentPage: this.context.currentPage(),
6148
+ selectedWidgetKey,
6149
+ provider: this.context.provider(),
6150
+ model: this.context.model(),
6151
+ apiKey: this.context.apiKey(),
6152
+ componentCapabilities,
6153
+ ...this.buildAuthoringContext(request),
6154
+ };
6155
+ }
6156
+ async toTurnResultFromStreamEvent(event) {
6157
+ const payload = this.toJsonObject(event.payload) ?? {};
6158
+ if (event.type === 'result') {
6159
+ return this.toResultTurnFromStreamPayload(payload);
6160
+ }
6161
+ if (event.type === 'error') {
6162
+ const message = this.describeStreamError(payload);
6163
+ return {
6164
+ state: 'error',
6165
+ phase: 'contextualize',
6166
+ assistantMessage: message,
6167
+ canApply: false,
6168
+ statusText: '',
6169
+ errorText: message,
6170
+ preview: null,
6171
+ diagnostics: { streamError: payload },
6172
+ };
6173
+ }
6174
+ if (event.type === 'cancelled') {
6175
+ return {
6176
+ state: 'listening',
6177
+ phase: 'capture',
6178
+ assistantMessage: this.context.tx('agentic.status.cancelled', 'Request cancelled.'),
6179
+ quickReplies: [],
6180
+ canApply: false,
6181
+ statusText: '',
6182
+ errorText: '',
6183
+ preview: null,
6184
+ pendingPatch: null,
6185
+ };
6186
+ }
6187
+ return {
6188
+ state: 'processing',
6189
+ phase: this.phaseForStreamPayload(payload),
6190
+ assistantMessage: undefined,
6191
+ canApply: false,
6192
+ statusText: this.statusForStreamPayload(payload),
6193
+ errorText: '',
6194
+ preview: null,
6195
+ };
6196
+ }
6197
+ async toResultTurnFromStreamPayload(payload) {
6198
+ const intentResolution = payload['intentResolution'];
6199
+ const preview = payload['preview'];
6200
+ if (!intentResolution) {
6201
+ const message = this.context.tx('agentic.errors.intentResolution', 'Intent could not be resolved.');
6202
+ return {
6203
+ state: 'error',
6204
+ phase: 'contextualize',
6205
+ assistantMessage: message,
6206
+ canApply: false,
6207
+ statusText: '',
6208
+ errorText: message,
6209
+ preview: null,
6210
+ };
6211
+ }
6212
+ const assistantMessage = this.readString(payload, 'assistantMessage')
6213
+ || this.resolveIntentAssistantMessage(intentResolution)
6214
+ || (preview ? this.context.describePreviewStatus(preview) : '');
6215
+ const quickReplies = Array.isArray(payload['quickReplies'])
6216
+ ? this.toShellQuickReplies(payload['quickReplies'])
6217
+ : [];
6218
+ const canApply = payload['canApply'] === true && !!preview?.valid;
6219
+ if (!canApply) {
6220
+ const pendingClarification = this.toShellPendingClarification(intentResolution?.pendingClarification);
6221
+ const requiresChoice = quickReplies.length > 0;
6222
+ return {
6223
+ state: pendingClarification || requiresChoice ? 'clarification' : 'success',
6224
+ phase: pendingClarification || requiresChoice ? 'clarify' : 'summarize',
6225
+ assistantMessage,
6226
+ quickReplies,
6227
+ canApply: false,
6228
+ statusText: '',
6229
+ errorText: '',
6230
+ preview: null,
6231
+ pendingClarification,
6232
+ diagnostics: { intentResolution, preview },
6233
+ };
6234
+ }
6235
+ const applied = await this.context.applyLocalPreview(preview);
6236
+ if (!applied.success) {
6237
+ const message = applied.error || this.context.tx('agentic.errors.applyLocal', 'Preview could not be applied.');
6238
+ return {
6239
+ state: 'error',
6240
+ phase: 'preview',
6241
+ assistantMessage: message,
6242
+ canApply: false,
6243
+ statusText: '',
6244
+ errorText: message,
6245
+ preview: null,
6246
+ diagnostics: { intentResolution, preview },
6247
+ };
6248
+ }
6249
+ const status = assistantMessage || this.context.describePreviewStatus(preview);
6250
+ return {
6251
+ state: 'review',
6252
+ phase: 'review',
6253
+ assistantMessage: status,
6254
+ quickReplies: [],
6255
+ canApply: true,
6256
+ statusText: status,
6257
+ errorText: '',
6258
+ preview,
6259
+ diagnostics: { intentResolution, preview },
6260
+ };
6261
+ }
6262
+ phaseForStreamPayload(payload) {
6263
+ const phase = this.readString(payload, 'phase');
6264
+ if (phase === 'intent.resolve')
6265
+ return 'contextualize';
6266
+ if (phase === 'resource.discovery')
6267
+ return 'contextualize';
6268
+ if (phase.startsWith('preview.'))
6269
+ return 'preview';
6270
+ if (phase === 'review')
6271
+ return 'review';
6272
+ return 'contextualize';
6273
+ }
6274
+ statusForStreamPayload(payload) {
6275
+ const phase = this.readString(payload, 'phase');
6276
+ switch (phase) {
6277
+ case 'context.bundle':
6278
+ return this.context.tx('agentic.status.contextBundle', 'Preparing context...');
6279
+ case 'intent.resolve':
6280
+ if (this.isSecondPassStreamPayload(payload)) {
6281
+ return this.context.tx('agentic.status.refinedCandidates', 'Reviewing the retrieved resources with the AI...');
6282
+ }
6283
+ return this.context.tx('agentic.status.resolvingIntent', 'Resolving intent...');
6284
+ case 'resource.discovery':
6285
+ if (this.isBackendResourceDiscoveryPayload(payload)) {
6286
+ return this.context.tx('agentic.status.resourceDiscoveryBackend', 'Found API resources in the backend catalog.');
6287
+ }
6288
+ return this.context.tx('agentic.status.resourceDiscovery', 'Finding API resources...');
6289
+ case 'preview.plan':
6290
+ return this.context.tx('agentic.status.previewing', 'Generating preview...');
6291
+ case 'preview.compile':
6292
+ return this.context.tx('agentic.status.previewCompile', 'Compiling preview...');
6293
+ default:
6294
+ return this.readString(payload, 'summary') || this.readString(payload, 'message');
6295
+ }
6296
+ }
6297
+ isBackendResourceDiscoveryPayload(payload) {
6298
+ const diagnostics = this.toJsonObject(payload['diagnostics']);
6299
+ return diagnostics?.['source'] === 'backend-resource-catalog';
6300
+ }
6301
+ isSecondPassStreamPayload(payload) {
6302
+ const diagnostics = this.toJsonObject(payload['diagnostics']);
6303
+ return diagnostics?.['secondPass'] === true;
6304
+ }
6305
+ describeStreamError(payload) {
6306
+ const code = this.readString(payload, 'code');
6307
+ if (code === 'agentic-authoring-timeout') {
6308
+ 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.');
6309
+ }
6310
+ if (code === 'agentic-authoring-processing-failed') {
6311
+ return this.context.tx('agentic.errors.streamProcessing', 'The assistant could not finish this authoring request. Try again or ask support to review the diagnostics.');
6312
+ }
6313
+ return this.readString(payload, 'assistantMessage')
6314
+ || this.context.tx('agentic.errors.generic', 'AI authoring failed.');
6315
+ }
6316
+ toTurnStreamTransportErrorResult(error) {
6317
+ const message = this.isTurnStreamConnectionError(error)
6318
+ ? 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.')
6319
+ : this.context.describeError(error);
6320
+ return {
6321
+ state: 'error',
6322
+ phase: 'contextualize',
6323
+ assistantMessage: message,
6324
+ canApply: false,
6325
+ statusText: '',
6326
+ errorText: message,
6327
+ preview: null,
6328
+ diagnostics: { streamTransportError: this.toJsonObject(error) ?? { reason: 'stream-transport-error' } },
6329
+ };
6330
+ }
6331
+ shouldFallbackFromTurnStreamError(error) {
6332
+ if (this.isTurnStreamConnectionError(error)) {
6333
+ return false;
6334
+ }
6335
+ const status = this.readErrorStatus(error);
6336
+ if (status === 401 || status === 403) {
6337
+ return false;
6338
+ }
6339
+ return status === 404 || status === 501 || status === 503;
6340
+ }
6341
+ isTurnStreamConnectionError(error) {
6342
+ return this.toJsonObject(error)?.['praxisAgenticTurnStreamConnectionError'] === true;
6343
+ }
6344
+ readErrorStatus(error) {
6345
+ const status = this.toJsonObject(error)?.['status'];
6346
+ return typeof status === 'number' && Number.isFinite(status) ? status : null;
6347
+ }
6348
+ async handleResourceDiscoveryTool(request, prompt) {
6349
+ const contextHints = this.toJsonObject(request.action?.contextHints) ?? {};
6350
+ const retrievalQuery = this.readString(contextHints, 'retrievalQuery') || prompt;
6351
+ const artifactKind = this.readString(contextHints, 'artifactKind') || null;
6352
+ const limit = this.readNumber(contextHints, 'limit');
6353
+ const result = await firstValueFrom(this.service.searchResourceCandidates({
6354
+ retrievalQuery,
6355
+ userPrompt: prompt,
6356
+ artifactKind,
6357
+ limit,
6358
+ }));
6359
+ const quickReplies = result.quickReplies?.length
6360
+ ? this.toShellQuickReplies(result.quickReplies)
6361
+ : this.toResourceCandidateQuickReplies(result.candidates, result.artifactKind);
6362
+ const assistantMessage = result.assistantMessage?.trim()
6363
+ || (quickReplies.length
6364
+ ? this.context.tx('agentic.resourceDiscovery.found', 'I found APIs that can feed this screen. Choose one before generating the preview.')
6365
+ : this.context.tx('agentic.resourceDiscovery.empty', 'I did not find a matching API yet. Describe the business data this screen should use.'));
6366
+ const intentResult = await this.resolveIntentAfterResourceDiscovery(request, request.pendingClarification?.sourcePrompt || prompt, contextHints, result, assistantMessage, quickReplies);
6367
+ if (intentResult) {
6368
+ return intentResult;
6369
+ }
6370
+ return {
6371
+ state: 'clarification',
6372
+ phase: 'clarify',
6373
+ assistantMessage,
6374
+ quickReplies,
6375
+ canApply: false,
6376
+ statusText: '',
6377
+ errorText: '',
6378
+ preview: null,
6379
+ pendingClarification: {
6380
+ sourcePrompt: request.pendingClarification?.sourcePrompt || prompt,
6381
+ assistantMessage,
6382
+ questions: [
6383
+ {
6384
+ id: 'resource',
6385
+ type: 'text',
6386
+ label: assistantMessage,
6387
+ },
6388
+ ],
6389
+ clientTurnId: request.clientTurnId,
6390
+ diagnostics: {
6391
+ resourceDiscovery: {
6392
+ tool: result.tool,
6393
+ retrievalQuery: result.retrievalQuery,
6394
+ artifactKind: result.artifactKind,
6395
+ warnings: result.warnings,
6396
+ },
6397
+ },
6398
+ },
6399
+ diagnostics: { resourceDiscovery: result },
6400
+ };
6401
+ }
6402
+ async resolveIntentAfterResourceDiscovery(request, prompt, contextHints, result, fallbackAssistantMessage, fallbackQuickReplies) {
6403
+ if (!result.valid || !result.candidates?.length) {
6404
+ return null;
6405
+ }
6406
+ const componentCapabilities = await this.context.loadComponentCapabilities();
6407
+ const authoringContext = this.buildAuthoringContext(request);
6408
+ const intentResolution = await firstValueFrom(this.service.resolveIntent(this.buildIntentResolutionRequest(prompt, this.context.selectedWidgetKey(), componentCapabilities, {
6409
+ ...authoringContext,
6410
+ contextHints: this.resourceDiscoveryContextHints(contextHints, result),
6411
+ })));
6412
+ const assistantMessage = this.resolveIntentAssistantMessage(intentResolution)
6413
+ || this.resolveIntentClarification(intentResolution)
6414
+ || fallbackAssistantMessage;
6415
+ const quickReplies = intentResolution.quickReplies?.length
6416
+ ? this.toShellQuickReplies(intentResolution.quickReplies)
6417
+ : fallbackQuickReplies;
6418
+ const explicitPendingClarification = this.toShellPendingClarification(intentResolution.pendingClarification);
6419
+ const pendingClarification = explicitPendingClarification
6420
+ ?? this.buildPendingClarificationForNextTurn(request, this.resolveEffectivePrompt(intentResolution, prompt), assistantMessage);
6421
+ if (!assistantMessage && quickReplies.length === 0) {
6422
+ return null;
6423
+ }
6424
+ return {
6425
+ state: quickReplies.length > 0 || this.hasPendingClarificationQuestion(pendingClarification)
6426
+ ? 'clarification'
6427
+ : 'success',
6428
+ phase: quickReplies.length > 0 || this.hasPendingClarificationQuestion(pendingClarification)
6429
+ ? 'clarify'
6430
+ : 'summarize',
6431
+ assistantMessage,
6432
+ quickReplies,
6433
+ canApply: false,
6434
+ statusText: '',
6435
+ errorText: '',
6436
+ preview: null,
6437
+ pendingClarification,
6438
+ diagnostics: { resourceDiscovery: result, intentResolution },
6439
+ };
6440
+ }
6441
+ resourceDiscoveryContextHints(contextHints, result) {
6442
+ const { tool: _tool, ...baseContextHints } = contextHints;
6443
+ return {
6444
+ ...baseContextHints,
6445
+ resourceDiscovery: {
6446
+ tool: result.tool,
6447
+ retrievalQuery: result.retrievalQuery,
6448
+ artifactKind: result.artifactKind,
6449
+ assistantMessage: result.assistantMessage ?? null,
6450
+ candidates: result.candidates.map((candidate) => ({
6451
+ resourcePath: candidate.resourcePath,
6452
+ operation: candidate.operation,
6453
+ schemaUrl: candidate.schemaUrl,
6454
+ submitUrl: candidate.submitUrl,
6455
+ submitMethod: candidate.submitMethod,
6456
+ score: candidate.score,
6457
+ reason: candidate.reason,
6458
+ evidence: candidate.evidence,
6459
+ })),
6460
+ quickReplies: (result.quickReplies ?? []).map((reply) => ({
6461
+ id: reply.id,
6462
+ kind: reply.kind,
6463
+ label: reply.label,
6464
+ prompt: reply.prompt,
6465
+ description: reply.description ?? null,
6466
+ icon: reply.icon ?? null,
6467
+ tone: reply.tone ?? null,
6468
+ contextHints: this.toJsonObject(reply.contextHints) ?? null,
6469
+ })),
6470
+ warnings: result.warnings,
6471
+ },
6472
+ };
6473
+ }
6474
+ isResourceDiscoveryTool(request) {
6475
+ const contextHints = this.toJsonObject(request.action?.contextHints);
6476
+ return this.readString(contextHints, 'tool') === 'searchApiResources';
6477
+ }
6478
+ toResourceCandidateQuickReplies(candidates, artifactKind) {
6479
+ return candidates
6480
+ .filter((candidate) => !!candidate.resourcePath?.trim())
6481
+ .map((candidate) => {
6482
+ const resourcePath = candidate.resourcePath.trim();
6483
+ const submitUrl = candidate.submitUrl?.trim() || resourcePath;
6484
+ return {
6485
+ id: this.resourceCandidateId(resourcePath),
6486
+ kind: 'suggestion',
6487
+ label: this.resourceCandidateLabel(resourcePath),
6488
+ prompt: this.formatResourceCandidatePrompt(resourcePath),
6489
+ description: this.resourceCandidateDescription(candidate),
6490
+ icon: 'dataset',
6491
+ tone: 'resource',
6492
+ contextHints: {
6493
+ resourcePath,
6494
+ submitUrl,
6495
+ operation: candidate.operation,
6496
+ schemaUrl: candidate.schemaUrl,
6497
+ submitMethod: candidate.submitMethod,
6498
+ artifactKind,
6499
+ },
6500
+ };
6501
+ });
6502
+ }
6503
+ formatResourceCandidatePrompt(resourcePath) {
6504
+ return this.context
6505
+ .tx('agentic.resourceDiscovery.useResource', 'Use {resourcePath}')
6506
+ .replace('{resourcePath}', resourcePath);
6507
+ }
6508
+ resourceCandidateId(resourcePath) {
6509
+ const slug = resourcePath
6510
+ .toLowerCase()
6511
+ .replace(/[^a-z0-9]+/g, '-')
6512
+ .replace(/^-+|-+$/g, '');
6513
+ return slug ? `resource-${slug}` : 'resource-candidate';
6514
+ }
6515
+ resourceCandidateLabel(resourcePath) {
6516
+ const segment = resourcePath.split('/').filter((part) => !!part).pop() || resourcePath;
6517
+ const label = segment
6518
+ .replace(/[-_]+/g, ' ')
6519
+ .replace(/\s+/g, ' ')
6520
+ .trim();
6521
+ return label
6522
+ ? label.charAt(0).toUpperCase() + label.slice(1)
6523
+ : resourcePath;
6524
+ }
6525
+ resourceCandidateDescription(candidate) {
6526
+ const method = candidate.submitMethod?.trim();
6527
+ const url = candidate.submitUrl?.trim();
6528
+ return method && url ? `${method} ${url}` : url || undefined;
6529
+ }
5863
6530
  resolveIntentAssistantMessage(intentResolution) {
6531
+ if (this.isExecutableIntent(intentResolution)) {
6532
+ return null;
6533
+ }
5864
6534
  const message = intentResolution.assistantMessage?.trim();
5865
6535
  return message || null;
5866
6536
  }
6537
+ isExecutableIntent(intentResolution) {
6538
+ const operationKind = intentResolution.operationKind;
6539
+ const artifactKind = intentResolution.artifactKind;
6540
+ return !!intentResolution.valid
6541
+ && intentResolution.gate?.status === 'eligible'
6542
+ && !!intentResolution.selectedCandidate
6543
+ && (artifactKind === 'form' || artifactKind === 'dashboard' || artifactKind === 'table' || artifactKind === 'page')
6544
+ && (operationKind === 'create' || operationKind === 'modify' || operationKind === 'remove' || operationKind === 'compose');
6545
+ }
5867
6546
  resolveIntentClarification(intentResolution) {
5868
6547
  const questions = intentResolution.clarificationQuestions?.filter((question) => !!question) ?? [];
5869
6548
  if (!intentResolution.valid && questions.length) {
@@ -5872,7 +6551,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
5872
6551
  return null;
5873
6552
  }
5874
6553
  describeIntentResolutionFailure(intentResolution) {
5875
- if (intentResolution.valid && intentResolution.gate?.status === 'eligible' && intentResolution.selectedCandidate) {
6554
+ if (this.isExecutableIntent(intentResolution)) {
5876
6555
  return null;
5877
6556
  }
5878
6557
  const questions = intentResolution.clarificationQuestions?.filter((question) => !!question) ?? [];
@@ -5897,6 +6576,10 @@ class PageBuilderAgenticAuthoringTurnFlow {
5897
6576
  kind: reply.kind,
5898
6577
  label: reply.label,
5899
6578
  prompt: reply.prompt,
6579
+ description: reply.description,
6580
+ icon: reply.icon,
6581
+ tone: reply.tone,
6582
+ contextHints: this.toJsonObject(reply.contextHints),
5900
6583
  }));
5901
6584
  }
5902
6585
  buildPendingClarificationForNextTurn(request, sourcePrompt, clarification) {
@@ -5947,16 +6630,47 @@ class PageBuilderAgenticAuthoringTurnFlow {
5947
6630
  diagnostics: this.toJsonObject(pending?.diagnostics) ?? undefined,
5948
6631
  };
5949
6632
  }
6633
+ hasPendingClarificationQuestion(pending) {
6634
+ return !!pending?.sourcePrompt?.trim() && pending.questions.some((question) => !!question.label?.trim());
6635
+ }
5950
6636
  resolveEffectivePrompt(intentResolution, fallbackPrompt) {
5951
6637
  return intentResolution.effectivePrompt?.trim() || fallbackPrompt;
5952
6638
  }
5953
6639
  buildAuthoringContext(request) {
6640
+ const contextHints = this.buildContextHints(request);
5954
6641
  return {
5955
6642
  sessionId: request.sessionId,
5956
6643
  clientTurnId: request.clientTurnId,
5957
6644
  conversationMessages: this.toConversationMessages(request.messages ?? []),
5958
6645
  pendingClarification: this.toPendingClarification(request.pendingClarification),
5959
6646
  attachmentSummaries: this.toAttachmentSummaries(request.attachments ?? []),
6647
+ contextHints,
6648
+ };
6649
+ }
6650
+ buildIntentResolutionRequest(prompt, selectedWidgetKey, componentCapabilities, authoringContext) {
6651
+ return {
6652
+ userPrompt: prompt,
6653
+ targetApp: this.context.targetApp,
6654
+ targetComponentId: this.context.targetComponentId,
6655
+ currentPage: this.context.currentPage(),
6656
+ selectedWidgetKey,
6657
+ provider: this.context.provider(),
6658
+ model: this.context.model(),
6659
+ apiKey: this.context.apiKey(),
6660
+ componentCapabilities,
6661
+ ...authoringContext,
6662
+ };
6663
+ }
6664
+ buildContextHints(request) {
6665
+ const base = this.toJsonObject(request.action?.contextHints)
6666
+ ?? this.toJsonObject(request.contextHints);
6667
+ const includeLlmDiagnostics = this.context.includeLlmDiagnostics?.() === true;
6668
+ if (!includeLlmDiagnostics) {
6669
+ return base ?? undefined;
6670
+ }
6671
+ return {
6672
+ ...(base ?? {}),
6673
+ includeLlmDiagnostics: true,
5960
6674
  };
5961
6675
  }
5962
6676
  toAttachmentSummaries(attachments) {
@@ -6005,6 +6719,14 @@ class PageBuilderAgenticAuthoringTurnFlow {
6005
6719
  ? value
6006
6720
  : null;
6007
6721
  }
6722
+ readString(value, key) {
6723
+ const raw = value?.[key];
6724
+ return typeof raw === 'string' ? raw.trim() : '';
6725
+ }
6726
+ readNumber(value, key) {
6727
+ const raw = value?.[key];
6728
+ return typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
6729
+ }
6008
6730
  }
6009
6731
 
6010
6732
  function buildConnectionsViewerModel(page) {
@@ -6017,6 +6739,12 @@ function buildConnectionsViewerModel(page) {
6017
6739
  const edges = links.map((link) => {
6018
6740
  const fromWidgetId = link.from.kind === 'component-port' ? link.from.ref.widget : null;
6019
6741
  const toWidgetId = link.to.kind === 'component-port' ? link.to.ref.widget : null;
6742
+ const fromNestedPath = link.from.kind === 'component-port' && link.from.ref.nestedPath?.length
6743
+ ? clone(link.from.ref.nestedPath)
6744
+ : null;
6745
+ const toNestedPath = link.to.kind === 'component-port' && link.to.ref.nestedPath?.length
6746
+ ? clone(link.to.ref.nestedPath)
6747
+ : null;
6020
6748
  const toStatePath = link.to.kind === 'state' ? link.to.ref.path : null;
6021
6749
  const diagnosticReasons = [];
6022
6750
  if (fromWidgetId && !counts.has(fromWidgetId)) {
@@ -6039,9 +6767,17 @@ function buildConnectionsViewerModel(page) {
6039
6767
  id: link.id,
6040
6768
  fromKind: link.from.kind,
6041
6769
  fromWidgetId,
6770
+ fromEndpointId: link.from.kind === 'component-port'
6771
+ ? buildComponentEndpointId(link.from.ref.widget, link.from.ref.nestedPath)
6772
+ : null,
6773
+ fromNestedPath,
6042
6774
  fromPort: link.from.kind === 'component-port' ? link.from.ref.port : null,
6043
6775
  toKind: link.to.kind,
6044
6776
  toWidgetId,
6777
+ toEndpointId: link.to.kind === 'component-port'
6778
+ ? buildComponentEndpointId(link.to.ref.widget, link.to.ref.nestedPath)
6779
+ : null,
6780
+ toNestedPath,
6045
6781
  toPort: link.to.kind === 'component-port' ? link.to.ref.port : null,
6046
6782
  toStatePath,
6047
6783
  intent: link.intent,
@@ -6072,6 +6808,31 @@ function buildConnectionsViewerModel(page) {
6072
6808
  diagnosticLinks: edges.filter((edge) => edge.hasDiagnostics).length,
6073
6809
  };
6074
6810
  }
6811
+ function buildComponentEndpointId(ownerWidgetKey, nestedPath) {
6812
+ if (!nestedPath?.length) {
6813
+ return ownerWidgetKey;
6814
+ }
6815
+ return `${ownerWidgetKey}${nestedPath
6816
+ .map((segment) => `/${formatPathSegment(segment)}`)
6817
+ .join('')}`;
6818
+ }
6819
+ function formatPathSegment(segment) {
6820
+ if (segment.kind === 'widget') {
6821
+ const identity = segment.key
6822
+ || segment.componentType
6823
+ || (typeof segment.index === 'number' ? String(segment.index) : 'unknown');
6824
+ return `widget:${identity}`;
6825
+ }
6826
+ return [
6827
+ segment.kind,
6828
+ segment.id || segment.key || segment.index,
6829
+ ].filter((part) => part !== undefined && part !== '').join(':');
6830
+ }
6831
+ function clone(value) {
6832
+ if (value == null || typeof value !== 'object')
6833
+ return value;
6834
+ return JSON.parse(JSON.stringify(value));
6835
+ }
6075
6836
 
6076
6837
  class ConnectionsViewerPanelComponent {
6077
6838
  i18n = inject(PraxisI18nService);
@@ -6522,6 +7283,8 @@ class DynamicPageBuilderComponent {
6522
7283
  agenticAuthoringComponentId;
6523
7284
  agenticAuthoringScope = 'user';
6524
7285
  agenticAuthoringEtag;
7286
+ agenticAuthoringIncludeLlmDiagnostics = false;
7287
+ agenticAuthoringEnableStreaming = false;
6525
7288
  pageChange = new EventEmitter();
6526
7289
  agenticAuthoringApplied = new EventEmitter();
6527
7290
  currentPage = signal({ widgets: [] }, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
@@ -6536,6 +7299,7 @@ class DynamicPageBuilderComponent {
6536
7299
  agenticAuthoringConversation = signal([], ...(ngDevMode ? [{ debugName: "agenticAuthoringConversation" }] : []));
6537
7300
  agenticAuthoringQuickReplies = signal([], ...(ngDevMode ? [{ debugName: "agenticAuthoringQuickReplies" }] : []));
6538
7301
  agenticAuthoringAttachments = signal([], ...(ngDevMode ? [{ debugName: "agenticAuthoringAttachments" }] : []));
7302
+ agenticAuthoringLlmDiagnostics = signal(null, ...(ngDevMode ? [{ debugName: "agenticAuthoringLlmDiagnostics" }] : []));
6539
7303
  agenticAuthoringEditingMessageId = signal(null, ...(ngDevMode ? [{ debugName: "agenticAuthoringEditingMessageId" }] : []));
6540
7304
  agenticAuthoringPanelLayout = signal({
6541
7305
  left: 16,
@@ -6568,6 +7332,7 @@ class DynamicPageBuilderComponent {
6568
7332
  this.previewMode = !this.previewMode;
6569
7333
  if (this.previewMode) {
6570
7334
  this.connectionsViewerOpen.set(false);
7335
+ this.agenticAuthoringLlmDiagnostics.set(null);
6571
7336
  }
6572
7337
  }
6573
7338
  toggleConnectionsViewer() {
@@ -6728,11 +7493,10 @@ class DynamicPageBuilderComponent {
6728
7493
  try {
6729
7494
  const controller = this.ensureAgenticTurnController();
6730
7495
  const editingMessageId = this.agenticAuthoringEditingMessageId();
6731
- const state = await firstValueFrom(editingMessageId
7496
+ await this.consumeAgenticTurn(editingMessageId
6732
7497
  ? controller.submitEditedMessage(editingMessageId, prompt)
6733
7498
  : controller.submitPrompt(prompt));
6734
7499
  this.agenticAuthoringEditingMessageId.set(null);
6735
- this.applyAgenticTurnState(state);
6736
7500
  }
6737
7501
  catch (error) {
6738
7502
  this.agenticAuthoringStatus.set('');
@@ -6745,24 +7509,79 @@ class DynamicPageBuilderComponent {
6745
7509
  }
6746
7510
  }
6747
7511
  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') {
7512
+ const replyKind = (reply.kind || 'suggestion').trim().toLowerCase();
7513
+ if (replyKind === 'cancel') {
6752
7514
  this.agenticAuthoringEditingMessageId.set(null);
6753
- const state = await firstValueFrom(this.ensureAgenticTurnController().cancel());
6754
- this.applyAgenticTurnState(state);
7515
+ await this.consumeAgenticTurn(this.ensureAgenticTurnController().cancel());
6755
7516
  return;
6756
7517
  }
6757
- if (reply.kind === 'revise') {
7518
+ if (replyKind === 'revise') {
6758
7519
  this.agenticAuthoringEditingMessageId.set(null);
6759
7520
  this.agenticAuthoringQuickReplies.set([]);
6760
7521
  this.agenticAuthoringStatus.set(this.tx('agentic.status.waitingRevision', 'Refine your prompt and preview again.'));
6761
7522
  return;
6762
7523
  }
6763
7524
  this.agenticAuthoringEditingMessageId.set(null);
6764
- this.agenticAuthoringPrompt.set(reply.prompt);
6765
- await this.previewAgenticAuthoring();
7525
+ const contextHints = reply.contextHints ? { ...reply.contextHints } : undefined;
7526
+ const visiblePrompt = this.agenticQuickReplyVisiblePrompt(reply, contextHints);
7527
+ this.agenticAuthoringPrompt.set(visiblePrompt);
7528
+ this.agenticAuthoringBusy.set(true);
7529
+ this.agenticAuthoringError.set('');
7530
+ this.agenticAuthoringPreviewResult.set(null);
7531
+ this.agenticAuthoringStatus.set(this.tx('agentic.status.resolvingIntent', 'Resolving intent...'));
7532
+ try {
7533
+ const controller = this.ensureAgenticTurnController();
7534
+ await this.consumeAgenticTurn(controller.snapshot().state === 'clarification'
7535
+ ? controller.answerClarification({
7536
+ id: reply.id,
7537
+ label: visiblePrompt,
7538
+ value: reply.prompt,
7539
+ description: reply.description ?? undefined,
7540
+ contextHints,
7541
+ })
7542
+ : controller.submitPrompt(visiblePrompt, {
7543
+ kind: replyKind || 'quick-reply',
7544
+ id: reply.id,
7545
+ value: reply.prompt,
7546
+ contextHints,
7547
+ }));
7548
+ }
7549
+ catch (error) {
7550
+ this.agenticAuthoringStatus.set('');
7551
+ const message = this.describeAgenticError(error);
7552
+ this.agenticAuthoringError.set(message);
7553
+ this.appendAgenticMessage('error', message);
7554
+ }
7555
+ finally {
7556
+ this.agenticAuthoringBusy.set(false);
7557
+ }
7558
+ }
7559
+ agenticQuickReplyVisiblePrompt(reply, contextHints) {
7560
+ if (!this.isResourceQuickReply(reply, contextHints)) {
7561
+ return reply.prompt;
7562
+ }
7563
+ const label = (reply.label || this.describeResourceContext(contextHints) || reply.prompt).trim();
7564
+ const resourcePath = String(contextHints?.['resourcePath'] || '').trim();
7565
+ return this.tx('agentic.quickReplies.resourceSelectionPrompt', 'Use {label} ({resourcePath}) as the data source.')
7566
+ .replace('{label}', label)
7567
+ .replace('{resourcePath}', resourcePath);
7568
+ }
7569
+ isResourceQuickReply(reply, contextHints) {
7570
+ return reply.kind === 'suggestion'
7571
+ && typeof contextHints?.['resourcePath'] === 'string'
7572
+ && !!String(contextHints['resourcePath']).trim();
7573
+ }
7574
+ describeResourceContext(contextHints) {
7575
+ const resourcePath = typeof contextHints?.['resourcePath'] === 'string'
7576
+ ? contextHints['resourcePath'].trim()
7577
+ : '';
7578
+ if (!resourcePath)
7579
+ return '';
7580
+ const segment = resourcePath.split('/').filter((part) => !!part).pop() || resourcePath;
7581
+ return segment
7582
+ .replace(/[-_]+/g, ' ')
7583
+ .replace(/\s+/g, ' ')
7584
+ .trim();
6766
7585
  }
6767
7586
  attachAgenticContext() {
6768
7587
  const controller = this.ensureAgenticTurnController();
@@ -6807,8 +7626,7 @@ class DynamicPageBuilderComponent {
6807
7626
  this.agenticAuthoringError.set('');
6808
7627
  this.agenticAuthoringPreviewResult.set(null);
6809
7628
  try {
6810
- const state = await firstValueFrom(this.ensureAgenticTurnController().resendMessage(message.id));
6811
- this.applyAgenticTurnState(state);
7629
+ await this.consumeAgenticTurn(this.ensureAgenticTurnController().resendMessage(message.id));
6812
7630
  }
6813
7631
  catch (error) {
6814
7632
  this.agenticAuthoringStatus.set('');
@@ -6877,6 +7695,8 @@ class DynamicPageBuilderComponent {
6877
7695
  provider: () => this.agenticAuthoringProvider,
6878
7696
  model: () => this.agenticAuthoringModel,
6879
7697
  apiKey: () => this.agenticAuthoringApiKey,
7698
+ enableTurnStream: () => this.agenticAuthoringEnableStreaming,
7699
+ includeLlmDiagnostics: () => this.agenticAuthoringIncludeLlmDiagnostics,
6880
7700
  loadComponentCapabilities: () => this.loadAgenticComponentCapabilities(),
6881
7701
  applyLocalPreview: (result) => this.applyAgenticPreviewLocally(result),
6882
7702
  describePreviewFailure: (result) => this.describeAgenticPreviewFailure(result),
@@ -6898,20 +7718,55 @@ class DynamicPageBuilderComponent {
6898
7718
  }
6899
7719
  async applyAgenticPreviewLocally(result) {
6900
7720
  const adapter = new PageBuilderAiAdapter(this.createAdapterHost(), this.componentMetadata);
6901
- return result.uiCompositionPlan
7721
+ const applied = await (result.uiCompositionPlan
6902
7722
  ? adapter.applyUiCompositionPlan(result.uiCompositionPlan)
6903
- : adapter.applyCompiledFormPatch(result.compiledFormPatch);
7723
+ : adapter.applyCompiledFormPatch(result.compiledFormPatch));
7724
+ if (!applied.success && this.isAgenticTableContractError(applied.error)) {
7725
+ return {
7726
+ ...applied,
7727
+ 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.'),
7728
+ };
7729
+ }
7730
+ return applied;
6904
7731
  }
6905
7732
  applyAgenticTurnState(state) {
7733
+ const preview = state.preview ?? null;
6906
7734
  this.agenticAuthoringConversation.set(state.messages);
6907
- this.agenticAuthoringQuickReplies.set(state.quickReplies);
7735
+ this.agenticAuthoringQuickReplies.set(preview?.valid ? [] : state.quickReplies);
6908
7736
  this.agenticAuthoringStatus.set(state.statusText);
6909
7737
  this.agenticAuthoringError.set(state.errorText);
6910
- this.agenticAuthoringPreviewResult.set(state.preview ?? null);
7738
+ this.agenticAuthoringPreviewResult.set(preview);
6911
7739
  this.agenticAuthoringAttachments.set(state.attachments);
7740
+ this.agenticAuthoringLlmDiagnostics.set(this.resolveAgenticLlmDiagnostics(state.diagnostics));
7741
+ }
7742
+ consumeAgenticTurn(states$) {
7743
+ return lastValueFrom(states$.pipe(tap((state) => this.applyAgenticTurnState(state))));
7744
+ }
7745
+ agenticAuthoringDiagnosticsTop() {
7746
+ return Math.max(16, this.agenticAuthoringPanelLayout().top);
7747
+ }
7748
+ agenticAuthoringLlmDiagnosticsText() {
7749
+ const diagnostics = this.agenticAuthoringLlmDiagnostics();
7750
+ if (!this.agenticAuthoringIncludeLlmDiagnostics || !diagnostics) {
7751
+ return '';
7752
+ }
7753
+ return JSON.stringify(diagnostics, null, 2);
7754
+ }
7755
+ resolveAgenticLlmDiagnostics(diagnostics) {
7756
+ if (!this.agenticAuthoringIncludeLlmDiagnostics) {
7757
+ return null;
7758
+ }
7759
+ const intentResolution = this.toRecord(diagnostics?.['intentResolution']);
7760
+ return this.toRecord(intentResolution?.['llmDiagnostics']);
7761
+ }
7762
+ toRecord(value) {
7763
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
7764
+ return null;
7765
+ }
7766
+ return value;
6912
7767
  }
6913
7768
  resolvePreviewCompiledFormPatch(preview) {
6914
- if (preview.uiCompositionPlan) {
7769
+ if (preview.uiCompositionPlan || preview.compiledFormPatch?.patch?.page) {
6915
7770
  return {
6916
7771
  ...preview.compiledFormPatch,
6917
7772
  patch: {
@@ -6920,9 +7775,6 @@ class DynamicPageBuilderComponent {
6920
7775
  },
6921
7776
  };
6922
7777
  }
6923
- if (preview.compiledFormPatch?.patch?.page) {
6924
- return preview.compiledFormPatch;
6925
- }
6926
7778
  return preview.compiledFormPatch;
6927
7779
  }
6928
7780
  focusCanvasWidget(widgetKey) {
@@ -6994,6 +7846,12 @@ class DynamicPageBuilderComponent {
6994
7846
  }
6995
7847
  }
6996
7848
  describeAgenticPreviewStatus(result) {
7849
+ const assistantMessage = typeof result.assistantMessage === 'string'
7850
+ ? result.assistantMessage.trim()
7851
+ : '';
7852
+ if (assistantMessage) {
7853
+ return assistantMessage;
7854
+ }
6997
7855
  switch (result.diagnostics?.fieldScopeDecision) {
6998
7856
  case 'accepted-add-local-field':
6999
7857
  return this.tx('agentic.status.acceptedAddLocalField', 'Local field added to the form.');
@@ -7005,6 +7863,11 @@ class DynamicPageBuilderComponent {
7005
7863
  return this.tx('agentic.status.previewReady', 'Preview applied to the page.');
7006
7864
  }
7007
7865
  }
7866
+ isAgenticTableContractError(error) {
7867
+ const message = typeof error === 'string' ? error.toLowerCase() : '';
7868
+ return message.includes('praxis-table')
7869
+ && (message.includes('unknown input') || message.includes('unknown inputs') || message.includes('unsupported'));
7870
+ }
7008
7871
  appendAgenticMessage(role, text) {
7009
7872
  const normalized = text.trim();
7010
7873
  if (!normalized)
@@ -7043,7 +7906,7 @@ class DynamicPageBuilderComponent {
7043
7906
  return resolvePraxisPageBuilderText(this.i18n, key, fallback);
7044
7907
  }
7045
7908
  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: [
7909
+ 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
7910
  providePraxisPageBuilderI18n(),
7048
7911
  { provide: DYNAMIC_PAGE_SHELL_EDITOR, useValue: WidgetShellEditorComponent },
7049
7912
  ], viewQueries: [{ propertyName: "runtime", first: true, predicate: ["runtime"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -7107,6 +7970,26 @@ class DynamicPageBuilderComponent {
7107
7970
  (close)="toggleAgenticAuthoring()"
7108
7971
  />
7109
7972
 
7973
+ <section
7974
+ *ngIf="agenticAuthoringIncludeLlmDiagnostics && agenticAuthoringLlmDiagnosticsText()"
7975
+ class="agentic-diagnostics-panel"
7976
+ data-testid="page-builder-agentic-llm-diagnostics"
7977
+ role="region"
7978
+ [attr.aria-label]="tx('agentic.diagnostics.title', 'LLM diagnostics')"
7979
+ [style.top.px]="agenticAuthoringDiagnosticsTop()"
7980
+ >
7981
+ <header class="agentic-diagnostics-panel__header">
7982
+ <span>{{ tx('agentic.diagnostics.title', 'LLM diagnostics') }}</span>
7983
+ <span class="agentic-diagnostics-panel__badge">
7984
+ {{ tx('agentic.diagnostics.badge', 'Debug') }}
7985
+ </span>
7986
+ </header>
7987
+ <p class="agentic-diagnostics-panel__description">
7988
+ {{ tx('agentic.diagnostics.description', 'Prompt, context bundle, and tool catalog returned by the backend for this turn.') }}
7989
+ </p>
7990
+ <pre>{{ agenticAuthoringLlmDiagnosticsText() }}</pre>
7991
+ </section>
7992
+
7110
7993
  <praxis-floating-toolbar
7111
7994
  [visible]="showSettings()"
7112
7995
  [canUndo]="false"
@@ -7141,7 +8024,7 @@ class DynamicPageBuilderComponent {
7141
8024
  </button>
7142
8025
  </praxis-floating-toolbar>
7143
8026
  </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"] }] });
8027
+ `, 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
8028
  }
7146
8029
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicPageBuilderComponent, decorators: [{
7147
8030
  type: Component,
@@ -7219,6 +8102,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7219
8102
  (close)="toggleAgenticAuthoring()"
7220
8103
  />
7221
8104
 
8105
+ <section
8106
+ *ngIf="agenticAuthoringIncludeLlmDiagnostics && agenticAuthoringLlmDiagnosticsText()"
8107
+ class="agentic-diagnostics-panel"
8108
+ data-testid="page-builder-agentic-llm-diagnostics"
8109
+ role="region"
8110
+ [attr.aria-label]="tx('agentic.diagnostics.title', 'LLM diagnostics')"
8111
+ [style.top.px]="agenticAuthoringDiagnosticsTop()"
8112
+ >
8113
+ <header class="agentic-diagnostics-panel__header">
8114
+ <span>{{ tx('agentic.diagnostics.title', 'LLM diagnostics') }}</span>
8115
+ <span class="agentic-diagnostics-panel__badge">
8116
+ {{ tx('agentic.diagnostics.badge', 'Debug') }}
8117
+ </span>
8118
+ </header>
8119
+ <p class="agentic-diagnostics-panel__description">
8120
+ {{ tx('agentic.diagnostics.description', 'Prompt, context bundle, and tool catalog returned by the backend for this turn.') }}
8121
+ </p>
8122
+ <pre>{{ agenticAuthoringLlmDiagnosticsText() }}</pre>
8123
+ </section>
8124
+
7222
8125
  <praxis-floating-toolbar
7223
8126
  [visible]="showSettings()"
7224
8127
  [canUndo]="false"
@@ -7253,7 +8156,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7253
8156
  </button>
7254
8157
  </praxis-floating-toolbar>
7255
8158
  </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"] }]
8159
+ `, 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
8160
  }], ctorParameters: () => [{ type: i1.MatDialog }, { type: undefined, decorators: [{
7258
8161
  type: Optional
7259
8162
  }, {
@@ -7294,6 +8197,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7294
8197
  type: Input
7295
8198
  }], agenticAuthoringEtag: [{
7296
8199
  type: Input
8200
+ }], agenticAuthoringIncludeLlmDiagnostics: [{
8201
+ type: Input
8202
+ }], agenticAuthoringEnableStreaming: [{
8203
+ type: Input
7297
8204
  }], pageChange: [{
7298
8205
  type: Output
7299
8206
  }], agenticAuthoringApplied: [{