@praxisui/table 8.0.0-beta.99 → 9.0.0-beta.0

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.
@@ -1,8 +1,9 @@
1
- import { firstValueFrom, Observable } from 'rxjs';
1
+ import { Observable, firstValueFrom } from 'rxjs';
2
2
 
3
3
  class TableAgenticAuthoringTurnFlow {
4
4
  adapter;
5
5
  aiApi;
6
+ turnClient;
6
7
  mode = 'agentic-authoring';
7
8
  filterFieldLabels = new Map();
8
9
  filterFieldCatalogEntries = [];
@@ -19,9 +20,10 @@ class TableAgenticAuthoringTurnFlow {
19
20
  streamTerminalReconnectLimit = 2;
20
21
  selectedRecordStreamTerminalWatchdogMs = 45_000;
21
22
  selectedRecordSurfaceStreamTerminalWatchdogMs = 18_000;
22
- constructor(adapter, aiApi) {
23
+ constructor(adapter, aiApi, turnClient) {
23
24
  this.adapter = adapter;
24
25
  this.aiApi = aiApi;
26
+ this.turnClient = turnClient;
25
27
  }
26
28
  submit(request) {
27
29
  const prompt = (request.prompt ?? '').trim();
@@ -50,7 +52,8 @@ class TableAgenticAuthoringTurnFlow {
50
52
  const schemaFields = this.adapter.getSchemaFields?.()
51
53
  ?.map((field) => this.toAiJsonObject(field))
52
54
  .filter((field) => Object.keys(field).length > 0);
53
- const contextHints = this.mergeJsonObjects(this.mergeJsonObjects(this.optionalJsonObject(this.adapter.getAuthoringContext?.()), this.tableConversationMemoryHints(request)), this.optionalJsonObject(request.action?.contextHints)) ?? null;
55
+ const baseContextHints = this.mergeJsonObjects(this.mergeJsonObjects(this.optionalJsonObject(this.adapter.getAuthoringContext?.()), this.tableConversationMemoryHints(request)), this.optionalJsonObject(request.action?.contextHints)) ?? null;
56
+ const contextHints = this.mergeJsonObjects(baseContextHints ?? undefined, this.componentPresentationTargetGroundingHints(prompt) ?? undefined) ?? baseContextHints;
54
57
  this.filterFieldCatalogEntries = this.extractFilterFieldCatalogEntries(contextHints);
55
58
  this.filterFieldLabels = this.extractFilterFieldLabels(this.filterFieldCatalogEntries);
56
59
  this.selectionDerivedFilterCandidateFields = this.extractSelectionDerivedFilterCandidateFields(contextHints);
@@ -82,11 +85,100 @@ class TableAgenticAuthoringTurnFlow {
82
85
  };
83
86
  };
84
87
  const progressPrompt = this.progressPromptFor(request, prompt);
88
+ if (this.canUseAgenticTurnStream()) {
89
+ return this.submitViaAgenticStream(request, progressPrompt, currentState, componentId, run);
90
+ }
85
91
  if (this.canUsePatchStream()) {
86
92
  return this.submitViaStream(request, progressPrompt, currentState, componentId, run);
87
93
  }
88
94
  return this.submitViaSnapshot(request, currentState, run);
89
95
  }
96
+ submitViaAgenticStream(request, prompt, currentState, componentId, buildRequest) {
97
+ return new Observable((subscriber) => {
98
+ let closed = false;
99
+ let subscription = null;
100
+ const close = () => {
101
+ closed = true;
102
+ subscription?.unsubscribe();
103
+ subscription = null;
104
+ };
105
+ const emitProgress = (statusText) => {
106
+ if (closed || !statusText.trim())
107
+ return;
108
+ subscriber.next({
109
+ state: 'processing',
110
+ phase: 'contextualize',
111
+ statusText,
112
+ canApply: false,
113
+ });
114
+ };
115
+ const fallbackToSnapshot = async () => {
116
+ if (closed)
117
+ return;
118
+ try {
119
+ emitProgress(this.buildSnapshotFallbackMessage(prompt, componentId));
120
+ const result = await this.submitViaSnapshot(request, currentState, buildRequest);
121
+ if (!closed) {
122
+ subscriber.next(result);
123
+ subscriber.complete();
124
+ }
125
+ }
126
+ catch (error) {
127
+ if (!closed) {
128
+ subscriber.next(this.toAssistantErrorResult(error));
129
+ subscriber.complete();
130
+ }
131
+ }
132
+ finally {
133
+ close();
134
+ }
135
+ };
136
+ void (async () => {
137
+ try {
138
+ emitProgress(this.buildInitialProgressMessage(prompt, componentId));
139
+ const { patchRequest, contextHints } = await buildRequest();
140
+ const localResponse = this.localRecommendedIntentResponse(request, contextHints ?? null);
141
+ if (localResponse) {
142
+ subscriber.next(this.toTurnResult(localResponse, request));
143
+ subscriber.complete();
144
+ close();
145
+ return;
146
+ }
147
+ if (this.shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints ?? null)) {
148
+ await fallbackToSnapshot();
149
+ return;
150
+ }
151
+ subscription = this.turnClient?.streamEvents(this.toAgenticAuthoringTurnRequest(patchRequest), {
152
+ resultTimeoutMs: this.streamTerminalWatchdogDelay(contextHints ?? null),
153
+ streamTimeoutMs: this.streamTerminalWatchdogDelay(contextHints ?? null),
154
+ }).subscribe({
155
+ next: (event) => {
156
+ if (closed)
157
+ return;
158
+ const terminal = this.toAgenticStreamTurnResult(event, request, currentState);
159
+ if (terminal) {
160
+ subscriber.next(terminal);
161
+ subscriber.complete();
162
+ close();
163
+ return;
164
+ }
165
+ const progress = this.toAgenticStreamProgress(event, prompt, componentId);
166
+ if (progress) {
167
+ emitProgress(progress);
168
+ }
169
+ },
170
+ error: () => {
171
+ void fallbackToSnapshot();
172
+ },
173
+ }) ?? null;
174
+ }
175
+ catch {
176
+ await fallbackToSnapshot();
177
+ }
178
+ })();
179
+ return () => close();
180
+ });
181
+ }
90
182
  async submitViaSnapshot(request, currentState, buildRequest) {
91
183
  const { patchRequest, contextHints } = await buildRequest();
92
184
  const localResponse = this.localRecommendedIntentResponse(request, contextHints ?? null);
@@ -317,16 +409,20 @@ class TableAgenticAuthoringTurnFlow {
317
409
  const guidedSelectedRecordsFilter = this.localGuidedSelectedRecordsFilterResponse(request, contextHints);
318
410
  if (guidedSelectedRecordsFilter)
319
411
  return guidedSelectedRecordsFilter;
412
+ if (this.selectedRecordLocalReadonlyAnalysisRequested(request)
413
+ && this.selectedRecordSampleRows(contextHints).length > 0) {
414
+ return this.selectedRecordsReadonlyInfoResponse(request, contextHints);
415
+ }
416
+ if (this.selectedRecordGenericCommonalityAnalysisRequested(request)
417
+ && this.selectedRecordSampleRows(contextHints).length > 0) {
418
+ return this.selectedRecordsGuidedAnalysisResponse(request, contextHints);
419
+ }
320
420
  const selectedRecordDerivedFilter = this.localSelectedRecordDerivedFilterResponse(request, contextHints);
321
421
  if (selectedRecordDerivedFilter)
322
422
  return selectedRecordDerivedFilter;
323
423
  const declaredFilter = this.localDeclaredFilterApplyResponse(request, contextHints);
324
424
  if (declaredFilter)
325
425
  return declaredFilter;
326
- if (this.selectedRecordGenericCommonalityAnalysisRequested(request)
327
- && this.selectedRecordSampleRows(contextHints).length > 0) {
328
- return this.selectedRecordsGuidedAnalysisResponse(request, contextHints);
329
- }
330
426
  const recommendedIntent = this.toRecord(contextHints?.['recommendedIntent']);
331
427
  const intentId = this.stringValue(recommendedIntent?.['id'])
332
428
  || this.stringValue(contextHints?.['opportunityId']);
@@ -398,15 +494,7 @@ class TableAgenticAuthoringTurnFlow {
398
494
  };
399
495
  }
400
496
  if (this.selectedRecordReadOnlyAnalysisRequested(request)) {
401
- return {
402
- type: 'info',
403
- sessionId: request.sessionId,
404
- message: this.selectedRecordsAnalyticalSummary(contextHints),
405
- warnings: [
406
- 'recommended-intent-local-selected-records-analysis',
407
- 'Resposta informativa gerada a partir do recommendedIntent canonico table.selection.analyze e selectedRecordsContext.sampleRows; nao houve roteamento textual primario.',
408
- ],
409
- };
497
+ return this.selectedRecordsReadonlyInfoResponse(request, contextHints);
410
498
  }
411
499
  const candidates = this.selectedRecordFilterCandidates(contextHints);
412
500
  const fields = candidates
@@ -433,6 +521,17 @@ class TableAgenticAuthoringTurnFlow {
433
521
  ],
434
522
  };
435
523
  }
524
+ selectedRecordsReadonlyInfoResponse(request, contextHints) {
525
+ return {
526
+ type: 'info',
527
+ sessionId: request.sessionId,
528
+ message: this.selectedRecordsAnalyticalSummary(contextHints),
529
+ warnings: [
530
+ 'recommended-intent-local-selected-records-analysis',
531
+ 'Resposta informativa gerada a partir do selectedRecordsContext.sampleRows e de uma intencao consultiva ja contextualizada; nao houve roteamento textual primario.',
532
+ ],
533
+ };
534
+ }
436
535
  localGuidedRuntimeOperationResponse(request, contextHints) {
437
536
  const runtimeOperation = this.toRecord(contextHints?.['tableRuntimeOperation']);
438
537
  const operationId = this.stringValue(runtimeOperation?.['operationId']);
@@ -2026,6 +2125,40 @@ class TableAgenticAuthoringTurnFlow {
2026
2125
  return typeof this.aiApi.startPatchStream === 'function'
2027
2126
  && typeof this.aiApi.connectPatchStream === 'function';
2028
2127
  }
2128
+ canUseAgenticTurnStream() {
2129
+ return typeof this.turnClient?.streamEvents === 'function';
2130
+ }
2131
+ toAgenticAuthoringTurnRequest(patchRequest) {
2132
+ return {
2133
+ ...patchRequest,
2134
+ targetApp: 'praxis-ui-angular',
2135
+ targetComponentId: patchRequest.componentId,
2136
+ currentPage: patchRequest.currentState,
2137
+ contextHints: patchRequest.contextHints ?? null,
2138
+ };
2139
+ }
2140
+ toAgenticStreamTurnResult(event, request, currentState) {
2141
+ if (event.kind !== 'stream-event') {
2142
+ return null;
2143
+ }
2144
+ return this.toTerminalStreamResult(event.event, request, currentState);
2145
+ }
2146
+ toAgenticStreamProgress(event, prompt, componentId) {
2147
+ if (event.kind === 'stream-started') {
2148
+ return this.buildStreamProgressMessage('status', { message: 'Stream iniciado.', phase: 'request' }, prompt, componentId);
2149
+ }
2150
+ if (event.kind === 'stream-lifecycle') {
2151
+ return this.buildStreamProgressMessage('status', {
2152
+ message: event.statusText,
2153
+ phase: event.phase,
2154
+ ...(event.diagnostics ?? {}),
2155
+ }, prompt, componentId);
2156
+ }
2157
+ if (event.event.type === 'heartbeat') {
2158
+ return null;
2159
+ }
2160
+ return this.buildStreamProgressMessage(event.event.type, this.toRecord(event.event.payload) ?? {}, prompt, componentId);
2161
+ }
2029
2162
  toTerminalStreamResult(event, request, currentState) {
2030
2163
  if (!event)
2031
2164
  return null;
@@ -2189,14 +2322,27 @@ class TableAgenticAuthoringTurnFlow {
2189
2322
  });
2190
2323
  }
2191
2324
  retry(request) {
2325
+ const retryAction = this.retryActionFromDiagnostics(request.diagnostics);
2192
2326
  const lastPrompt = [...(request.messages ?? [])].reverse()
2193
2327
  .find((message) => message.role === 'user')?.text;
2194
2328
  return this.submit({
2195
2329
  ...request,
2196
- prompt: lastPrompt ?? request.prompt,
2197
- action: { kind: 'retry' },
2330
+ prompt: this.stringValue(retryAction?.['value']) || lastPrompt || request.prompt,
2331
+ action: retryAction
2332
+ ? { ...retryAction, kind: this.stringValue(retryAction['kind']) || 'clarify' }
2333
+ : { kind: 'retry' },
2198
2334
  });
2199
2335
  }
2336
+ retryActionFromDiagnostics(diagnostics) {
2337
+ const retryAction = this.toRecord(diagnostics?.['retryAction']);
2338
+ if (!retryAction)
2339
+ return null;
2340
+ const contextHints = this.toRecord(retryAction['contextHints']);
2341
+ return {
2342
+ ...retryAction,
2343
+ ...(contextHints ? { contextHints } : {}),
2344
+ };
2345
+ }
2200
2346
  toTurnResult(response, request) {
2201
2347
  if (!response) {
2202
2348
  return {
@@ -2261,6 +2407,7 @@ class TableAgenticAuthoringTurnFlow {
2261
2407
  }
2262
2408
  if (response.type === 'error') {
2263
2409
  const message = response.message || 'Falha ao gerar alteração de tabela.';
2410
+ const retryDiagnostics = this.errorRetryDiagnostics(request, response);
2264
2411
  return {
2265
2412
  state: 'error',
2266
2413
  phase: 'capture',
@@ -2268,7 +2415,7 @@ class TableAgenticAuthoringTurnFlow {
2268
2415
  observationId: response.observationId ?? request.observationId ?? null,
2269
2416
  assistantMessage: message,
2270
2417
  errorText: message,
2271
- diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
2418
+ diagnostics: retryDiagnostics,
2272
2419
  };
2273
2420
  }
2274
2421
  if (response.patch && Object.keys(response.patch).length > 0) {
@@ -2320,10 +2467,26 @@ class TableAgenticAuthoringTurnFlow {
2320
2467
  }
2321
2468
  compileAdapterResponse(response, request) {
2322
2469
  response = this.normalizeCategoricalRendererPalette(response, request);
2470
+ const incompatibleBooleanStatusPresentation = this.incompatibleBooleanStatusPresentationBoundary(response, request);
2471
+ if (incompatibleBooleanStatusPresentation) {
2472
+ return incompatibleBooleanStatusPresentation;
2473
+ }
2474
+ const visualPresentationTargetMismatch = this.visualPresentationTargetMismatchBoundary(response, request);
2475
+ if (visualPresentationTargetMismatch) {
2476
+ return visualPresentationTargetMismatch;
2477
+ }
2323
2478
  response = this.normalizeStatusPresentationPlan(response, request);
2324
2479
  response = this.normalizeNumericBandConditionalStylePlan(response, request);
2325
2480
  response = this.normalizeCompoundColumnPresentationPlan(response, request);
2326
2481
  response = this.normalizeComputedColumnAuxiliaryMaterializations(response);
2482
+ const selectedRecordReadonlyPresentationBoundary = this.selectedRecordReadonlyPresentationBoundaryResponse(response, request);
2483
+ if (selectedRecordReadonlyPresentationBoundary) {
2484
+ return selectedRecordReadonlyPresentationBoundary;
2485
+ }
2486
+ const visualPresentationChoice = this.visualPresentationChoiceClarificationBoundary(response, request);
2487
+ if (visualPresentationChoice) {
2488
+ return visualPresentationChoice;
2489
+ }
2327
2490
  if (response.type === 'clarification' || response.type === 'info' || response.type === 'error') {
2328
2491
  const selectedRecordReadonlyClarificationBoundary = this.selectedRecordReadonlyClarificationBoundaryResponse(response, request);
2329
2492
  if (selectedRecordReadonlyClarificationBoundary) {
@@ -2333,6 +2496,22 @@ class TableAgenticAuthoringTurnFlow {
2333
2496
  if (continuedEarlyColumnPlan) {
2334
2497
  return this.compileAdapterResponse(continuedEarlyColumnPlan, request);
2335
2498
  }
2499
+ const governedCategoricalSemanticsChoice = this.governedCategoricalSemanticsClarificationBoundary(response);
2500
+ if (governedCategoricalSemanticsChoice) {
2501
+ return governedCategoricalSemanticsChoice;
2502
+ }
2503
+ const governedCategoricalDiscovery = this.governedCategoricalDiscoveryBoundary(response, request);
2504
+ if (governedCategoricalDiscovery) {
2505
+ return governedCategoricalDiscovery;
2506
+ }
2507
+ const canonicalRendererSelectionBoundary = this.canonicalRendererSelectionBoundary(response, request);
2508
+ if (canonicalRendererSelectionBoundary) {
2509
+ return canonicalRendererSelectionBoundary;
2510
+ }
2511
+ const governedCategoricalSelectionBoundary = this.governedCategoricalSelectionBoundary(response, request);
2512
+ if (governedCategoricalSelectionBoundary) {
2513
+ return governedCategoricalSelectionBoundary;
2514
+ }
2336
2515
  const continuedColumnVisibilityPlan = this.columnVisibilityPlanForGroundedClarification(response, request);
2337
2516
  if (continuedColumnVisibilityPlan) {
2338
2517
  return this.compileAdapterResponse(continuedColumnVisibilityPlan, request);
@@ -2375,7 +2554,7 @@ class TableAgenticAuthoringTurnFlow {
2375
2554
  if (selectedRecordReadonlyBoundary) {
2376
2555
  return selectedRecordReadonlyBoundary;
2377
2556
  }
2378
- return executableResponse;
2557
+ return this.normalizeGovernedCategoricalSelectionExecutableResponse(executableResponse, request);
2379
2558
  }
2380
2559
  if (compiledExecutable?.type === 'error') {
2381
2560
  const continuedColumnVisibilityError = this.columnVisibilityPlanForMisroutedSurfaceError(response, request, compiledExecutable);
@@ -2581,7 +2760,93 @@ class TableAgenticAuthoringTurnFlow {
2581
2760
  return visualSurfaceMismatch;
2582
2761
  }
2583
2762
  const openOnlySurfaceRuntimeOperation = this.selectedRecordSurfaceRuntimeOperationForOpenOnlyExecutable(compiledResponse, request);
2584
- return openOnlySurfaceRuntimeOperation ?? compiledResponse;
2763
+ if (openOnlySurfaceRuntimeOperation)
2764
+ return openOnlySurfaceRuntimeOperation;
2765
+ return this.normalizeGovernedCategoricalSelectionExecutableResponse(compiledResponse, request);
2766
+ }
2767
+ visualPresentationChoiceClarificationBoundary(response, request) {
2768
+ const carriesVisualPayload = (response.optionPayloads ?? [])
2769
+ .some((payload) => this.isVisualPresentationClarificationPayload(payload));
2770
+ if (!carriesVisualPayload)
2771
+ return null;
2772
+ if (!this.promptRequestsPresentationOptions(request?.prompt ?? ''))
2773
+ return null;
2774
+ const message = response.message || 'Encontrei opções de apresentação para essa coluna. Escolha uma opção para aplicar.';
2775
+ return {
2776
+ ...response,
2777
+ type: 'clarification',
2778
+ message,
2779
+ explanation: message,
2780
+ componentEditPlan: undefined,
2781
+ patch: undefined,
2782
+ warnings: [
2783
+ ...(response.warnings ?? []),
2784
+ 'visual-presentation-choice-kept-as-clarification',
2785
+ ],
2786
+ };
2787
+ }
2788
+ governedCategoricalSemanticsClarificationBoundary(response) {
2789
+ if (!this.responseHasGovernedCategoricalSemanticsChoice(response))
2790
+ return null;
2791
+ const message = response.message
2792
+ || 'Esta apresentação precisa de uma decisão semântica governada antes de materializar a tabela.';
2793
+ return {
2794
+ ...response,
2795
+ type: 'clarification',
2796
+ message,
2797
+ explanation: message,
2798
+ componentEditPlan: undefined,
2799
+ patch: undefined,
2800
+ warnings: [
2801
+ ...(response.warnings ?? []),
2802
+ 'governed-categorical-semantics-choice-kept-as-clarification',
2803
+ ],
2804
+ };
2805
+ }
2806
+ responseHasGovernedCategoricalSemanticsChoice(response) {
2807
+ if ((response.optionPayloads ?? [])
2808
+ .some((payload) => this.isGovernedCategoricalSemanticsChoicePayload(payload))) {
2809
+ return true;
2810
+ }
2811
+ return this.isGovernedCategoricalSemanticsChoiceText([
2812
+ response.message,
2813
+ response.explanation,
2814
+ ...(response.questions ?? []),
2815
+ ...(response.options ?? []),
2816
+ ].filter(Boolean).join(' '));
2817
+ }
2818
+ promptRequestsPresentationOptions(prompt) {
2819
+ const normalized = this.normalizeLabel(prompt);
2820
+ if (!normalized)
2821
+ return false;
2822
+ const asksForOptions = [
2823
+ 'opcao',
2824
+ 'opcoes',
2825
+ 'opção',
2826
+ 'opções',
2827
+ 'alternativa',
2828
+ 'alternativas',
2829
+ 'recomenda',
2830
+ 'recomende',
2831
+ 'sugere',
2832
+ 'sugira',
2833
+ 'compare',
2834
+ ].some((token) => this.normalizedTextContainsApproxPhrase(normalized, token));
2835
+ if (!asksForOptions)
2836
+ return false;
2837
+ return [
2838
+ 'apresentacao',
2839
+ 'apresentação',
2840
+ 'formatacao',
2841
+ 'formatação',
2842
+ 'badge',
2843
+ 'icone',
2844
+ 'ícone',
2845
+ 'alinhamento',
2846
+ 'duas linhas',
2847
+ 'duaslinhas',
2848
+ 'visual',
2849
+ ].some((token) => this.normalizedTextContainsApproxPhrase(normalized, token));
2585
2850
  }
2586
2851
  normalizeCategoricalRendererPalette(response, request) {
2587
2852
  const plan = this.toRecord(response.componentEditPlan);
@@ -2711,7 +2976,7 @@ class TableAgenticAuthoringTurnFlow {
2711
2976
  relativeColumnOrderPlanForMisroutedClarification(response, request) {
2712
2977
  if (!request?.prompt)
2713
2978
  return null;
2714
- const prompt = this.normalizeLabel(request.prompt);
2979
+ const prompt = this.normalizeLabel(request.prompt ?? '');
2715
2980
  const position = this.relativeOrderPosition(prompt);
2716
2981
  if (!position)
2717
2982
  return null;
@@ -4703,6 +4968,8 @@ class TableAgenticAuthoringTurnFlow {
4703
4968
  const responseType = this.stringValue(response.type);
4704
4969
  if (!request || !['clarification', 'info', 'success'].includes(responseType))
4705
4970
  return null;
4971
+ if (this.requestIsCanonicalRendererSelection(request))
4972
+ return null;
4706
4973
  const responseText = [
4707
4974
  response.message ?? '',
4708
4975
  response.explanation ?? '',
@@ -4737,7 +5004,9 @@ class TableAgenticAuthoringTurnFlow {
4737
5004
  ?? this.booleanSimNaoOperationsFromGroundedClarification(prompt, responseText, columns)
4738
5005
  ?? this.booleanSimNaoOperationsFromSingleGroundedBooleanColumn(prompt, responseText, columns)
4739
5006
  ?? this.booleanSimNaoOperationsFromGroundedResponseField(prompt, responseText, columns);
4740
- const statusPresentationOperations = this.statusPresentationOperationsFromPrompt(prompt, columns);
5007
+ const statusPresentationOperations = this.responseCarriesClarificationChoices(response)
5008
+ ? []
5009
+ : this.statusPresentationOperationsFromPrompt(prompt, columns);
4741
5010
  if (statusPresentationOperations.length) {
4742
5011
  return this.componentEditPlanResponse(statusPresentationOperations, 'Vou ajustar a apresentacao visual do status sem filtrar as linhas.', [
4743
5012
  ...(response.warnings ?? []),
@@ -4813,6 +5082,9 @@ class TableAgenticAuthoringTurnFlow {
4813
5082
  ]);
4814
5083
  }
4815
5084
  const categoricalRenderer = this.categoricalChipOperationsFromVisualClarification(prompt, responseText, columns);
5085
+ if (categoricalRenderer.length && this.responseHasGovernedCategoricalSemanticsChoice(response)) {
5086
+ return null;
5087
+ }
4816
5088
  if (categoricalRenderer.length) {
4817
5089
  return this.componentEditPlanResponse(categoricalRenderer, 'Vou aplicar chips discretos na coluna indicada.', [
4818
5090
  ...(response.warnings ?? []),
@@ -4888,6 +5160,14 @@ class TableAgenticAuthoringTurnFlow {
4888
5160
  return response;
4889
5161
  if ((response.warnings ?? []).includes('status-presentation-normalized'))
4890
5162
  return response;
5163
+ if (this.requestIsCanonicalRendererSelection(request))
5164
+ return response;
5165
+ if (this.requestHasGovernedCategoricalSelection(request))
5166
+ return response;
5167
+ if ((response.optionPayloads?.length ?? 0) > 0)
5168
+ return response;
5169
+ if (this.responseHasGovernedCategoricalSemanticsChoice(response))
5170
+ return response;
4891
5171
  const currentConfig = this.adapter.getCurrentConfig?.();
4892
5172
  const columns = Array.isArray(currentConfig?.['columns'])
4893
5173
  ? currentConfig['columns']
@@ -4933,93 +5213,665 @@ class TableAgenticAuthoringTurnFlow {
4933
5213
  ],
4934
5214
  };
4935
5215
  }
4936
- normalizeNumericBandConditionalStylePlan(response, request) {
4937
- if (!request)
4938
- return response;
4939
- if ((response.warnings ?? []).includes('numeric-band-conditional-style-normalized'))
4940
- return response;
4941
- const currentConfig = this.adapter.getCurrentConfig?.();
4942
- const columns = Array.isArray(currentConfig?.['columns'])
4943
- ? currentConfig['columns']
4944
- .map((column) => this.toRecord(column))
4945
- .filter((column) => !!column && !!this.stringValue(column['field']))
4946
- : [];
4947
- if (!columns.length)
4948
- return response;
4949
- const operations = this.numericBandConditionalStyleOperationsFromPrompt(this.componentPresentationPromptForTurn(request), [
4950
- response.message,
4951
- response.explanation,
4952
- ...(response.questions ?? []),
4953
- ].join(' '), columns);
4954
- if (!operations.length)
4955
- return response;
4956
- const fields = new Set(operations
4957
- .map((operation) => this.operationTargetField(operation))
4958
- .filter((field) => !!field));
4959
- const originalPlan = this.toRecord(response.componentEditPlan);
4960
- const originalOperations = this.componentEditOperations(originalPlan)
4961
- .filter((operation) => {
4962
- const field = this.operationTargetField(operation);
4963
- return !(field && fields.has(field) && this.stringValue(operation['operationId']) === 'column.conditionalStyle.add');
4964
- });
5216
+ requestIsCanonicalRendererSelection(request) {
5217
+ if (this.requestHasGovernedCategoricalSelection(request))
5218
+ return false;
5219
+ const actionHints = this.toRecord(request.action?.contextHints);
5220
+ const requestHints = this.toRecord(request.contextHints);
5221
+ const actionSelection = this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection']);
5222
+ const requestSelection = this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
5223
+ const mode = this.normalizeLabel(this.stringValue(actionSelection?.['mode']) || this.stringValue(requestSelection?.['mode']));
5224
+ if (mode === 'renderer')
5225
+ return true;
5226
+ const prompt = this.normalizeLabel(request.prompt ?? '');
5227
+ return this.normalizedTextIncludesAny(prompt, [
5228
+ 'two_lines',
5229
+ 'two lines',
5230
+ 'two line',
5231
+ 'two-line',
5232
+ 'twolines',
5233
+ 'twoline',
5234
+ 'duas linhas',
5235
+ 'duas_linhas',
5236
+ 'duaslinhas',
5237
+ 'segunda linha',
5238
+ ]);
5239
+ }
5240
+ canonicalRendererSelectionBoundary(response, request) {
5241
+ if (!request || !this.requestIsCanonicalRendererSelection(request))
5242
+ return null;
5243
+ if (response.type === 'error')
5244
+ return null;
5245
+ if (this.responseCarriesClarificationChoices(response))
5246
+ return null;
5247
+ const selection = this.canonicalRendererSelection(request);
5248
+ const field = this.stringValue(selection?.['field']);
5249
+ if (!field)
5250
+ return null;
5251
+ const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
5252
+ || this.componentEditOperations(response.componentEditPlan).length > 0
5253
+ || !!this.toRecord(response.patch);
5254
+ if (hasExecutableEnvelope && this.responseTargetsSelectedRendererField(response, field)) {
5255
+ return null;
5256
+ }
5257
+ const label = this.stringValue(request.action?.displayPrompt)
5258
+ || this.stringValue(request.action?.value)
5259
+ || this.stringValue(selection?.['value'])
5260
+ || 'opção selecionada';
5261
+ const header = this.humanizeField(field);
4965
5262
  return {
4966
- ...response,
4967
- ...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
4968
- componentEditPlan: {
4969
- ...(originalPlan ?? {}),
4970
- kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
4971
- version: this.stringValue(originalPlan?.['version']) || '1.0',
4972
- componentId: this.stringValue(originalPlan?.['componentId'])
4973
- || this.adapter.componentId
4974
- || request.componentId
4975
- || 'praxis-table',
4976
- operations: [...originalOperations, ...operations],
4977
- },
4978
- explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
5263
+ type: 'info',
5264
+ sessionId: response.sessionId,
5265
+ observationId: response.observationId,
5266
+ message: `A opção "${label}" foi selecionada, mas a resposta ainda não trouxe um plano governado aplicável para ${header}.`,
4979
5267
  warnings: [
4980
5268
  ...(response.warnings ?? []),
4981
- 'numeric-band-conditional-style-normalized',
4982
- 'Residual continuity guard grounded numeric band thresholds from the canonical dataProfile when available, avoiding accidental thresholds from prose or visual token numbers.',
5269
+ 'canonical-renderer-selection-kept-non-executable',
5270
+ 'A guided renderer option was selected from canonical optionSelected context; the returned answer did not target the selected field with an executable presentation plan, so the table runtime kept the turn non-applicable instead of synthesizing a status boolean patch.',
4983
5271
  ],
4984
5272
  };
4985
5273
  }
4986
- componentEditPlanHasAnyOperation(componentEditPlan, operationIds) {
4987
- if (!operationIds.length)
4988
- return false;
4989
- const expected = new Set(operationIds);
4990
- return this.componentEditOperations(componentEditPlan)
4991
- .some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
5274
+ canonicalRendererSelection(request) {
5275
+ const actionHints = this.toRecord(request.action?.contextHints);
5276
+ const requestHints = this.toRecord(request.contextHints);
5277
+ return this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection'])
5278
+ ?? this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
4992
5279
  }
4993
- responseHasComputedColumnAuthoringDecision(response) {
4994
- if (this.componentEditPlanHasAnyOperation(response.componentEditPlan, ['column.computed.add', 'column.computed.set'])) {
5280
+ responseTargetsSelectedRendererField(response, field) {
5281
+ const normalizedField = this.normalizeLabel(field);
5282
+ if (!normalizedField)
5283
+ return false;
5284
+ const operations = this.componentEditOperations(response.componentEditPlan);
5285
+ if (operations.some((operation) => this.normalizeLabel(this.operationTargetField(operation)) === normalizedField)) {
4995
5286
  return true;
4996
5287
  }
4997
5288
  const patch = this.toRecord(response.patch);
4998
- const columns = Array.isArray(patch?.['columns']) ? patch['columns'] : [];
4999
- return columns
5289
+ const columns = this.toArray(patch?.['columns'])
5000
5290
  .map((column) => this.toRecord(column))
5001
- .some((column) => !!column?.['computed']);
5291
+ .filter((column) => !!column);
5292
+ return columns.some((column) => this.normalizeLabel(this.stringValue(column['field'])) === normalizedField);
5002
5293
  }
5003
- normalizeComputedColumnAuxiliaryMaterializations(response) {
5004
- const plan = this.toRecord(response.componentEditPlan);
5005
- const operations = this.componentEditOperations(plan);
5006
- if (!plan || operations.length < 2)
5007
- return response;
5008
- const computedFields = new Set(operations
5009
- .filter((operation) => this.componentEditPlanOperationIdentity(operation) === 'column.computed.add'
5010
- || this.componentEditPlanOperationIdentity(operation) === 'column.computed.set')
5011
- .map((operation) => this.operationTargetField(operation))
5012
- .filter((field) => !!field));
5013
- if (!computedFields.size) {
5014
- return response;
5015
- }
5016
- const auxiliaryAddedFields = new Set();
5017
- for (const operation of operations) {
5018
- const operationId = this.componentEditPlanOperationIdentity(operation);
5019
- const field = this.operationTargetField(operation);
5020
- if (!field)
5021
- continue;
5022
- if (operationId === 'column.add' && !computedFields.has(field)) {
5294
+ governedCategoricalSelectionBoundary(response, request) {
5295
+ if (!request || !this.requestHasGovernedCategoricalSelection(request))
5296
+ return null;
5297
+ if (response.type === 'error')
5298
+ return null;
5299
+ if (this.responseCarriesClarificationChoices(response))
5300
+ return null;
5301
+ const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
5302
+ || this.componentEditOperations(response.componentEditPlan).length > 0
5303
+ || !!this.toRecord(response.patch);
5304
+ if (hasExecutableEnvelope)
5305
+ return null;
5306
+ const field = this.governedCategoricalSelectionTargetField(request, response);
5307
+ return {
5308
+ type: 'info',
5309
+ sessionId: response.sessionId,
5310
+ observationId: response.observationId,
5311
+ message: field
5312
+ ? `A opção governada foi selecionada, mas a resposta ainda não trouxe um plano aplicável para ${this.humanizeField(field)}.`
5313
+ : 'A opção governada foi selecionada, mas a resposta ainda não trouxe um plano aplicável para esta coluna.',
5314
+ warnings: [
5315
+ ...(response.warnings ?? []),
5316
+ 'governed-categorical-selection-kept-non-executable',
5317
+ ],
5318
+ };
5319
+ }
5320
+ governedCategoricalDiscoveryBoundary(response, request) {
5321
+ if (!request || !this.requestHasGovernedCategoricalSelection(request))
5322
+ return null;
5323
+ if (!this.requestSelectsCategoricalFieldSemanticsDiscovery(request))
5324
+ return null;
5325
+ if (response.type === 'error')
5326
+ return null;
5327
+ if (this.responseCarriesClarificationChoices(response))
5328
+ return null;
5329
+ const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
5330
+ || this.componentEditOperations(response.componentEditPlan).length > 0
5331
+ || !!this.toRecord(response.patch);
5332
+ if (hasExecutableEnvelope)
5333
+ return null;
5334
+ const field = this.governedCategoricalSelectionTargetField(request, response);
5335
+ if (!field)
5336
+ return null;
5337
+ const column = this.currentConfigColumns()
5338
+ .find((candidate) => this.stringValue(candidate['field']) === field);
5339
+ const label = this.stringValue(column?.['header']) || this.humanizeField(field);
5340
+ const values = this.currentCategoricalValuesForField(field);
5341
+ const valuesMessage = values.length
5342
+ ? `Valores observados em ${label}: ${values.join(', ')}.`
5343
+ : `Ainda não recebi amostras de valores para ${label}.`;
5344
+ return {
5345
+ type: 'clarification',
5346
+ sessionId: response.sessionId ?? request.sessionId,
5347
+ observationId: response.observationId ?? request.observationId ?? null,
5348
+ message: `${valuesMessage} Posso aplicar uma apresentação categórica neutra agora; para ícones, cores ou prioridade por valor, preciso de uma política semântica governada para estes valores.`,
5349
+ questions: [`Como quer materializar ${label} agora?`],
5350
+ optionPayloads: [
5351
+ this.categoricalPresentationOptionPayload(field, label, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${label}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
5352
+ ],
5353
+ warnings: [
5354
+ ...(response.warnings ?? []),
5355
+ 'governed-categorical-discovery-grounded-in-current-rows',
5356
+ 'The guided categorical discovery option was continued with current table values and kept non-executable because no governed value-level semantic policy was returned.',
5357
+ ],
5358
+ };
5359
+ }
5360
+ normalizeGovernedCategoricalSelectionExecutableResponse(response, request) {
5361
+ if (!request || !this.requestHasGovernedCategoricalSelection(request))
5362
+ return response;
5363
+ if (this.requestSelectsCategoricalFieldSemanticsDiscovery(request)) {
5364
+ return this.governedCategoricalDiscoveryBoundary({
5365
+ type: 'info',
5366
+ sessionId: response.sessionId,
5367
+ observationId: response.observationId ?? null,
5368
+ warnings: [
5369
+ ...(response.warnings ?? []),
5370
+ 'governed-categorical-discovery-executable-response-blocked',
5371
+ ],
5372
+ }, request) ?? {
5373
+ type: 'info',
5374
+ sessionId: response.sessionId,
5375
+ observationId: response.observationId ?? null,
5376
+ message: 'A descoberta governada foi mantida sem aplicação porque a resposta retornou um plano executável antes de uma política semântica por valor.',
5377
+ warnings: [
5378
+ ...(response.warnings ?? []),
5379
+ 'governed-categorical-discovery-executable-response-blocked',
5380
+ ],
5381
+ };
5382
+ }
5383
+ const field = this.governedCategoricalSelectionTargetField(request, response);
5384
+ if (field && !this.responseTargetsSelectedRendererField(response, field)) {
5385
+ return {
5386
+ type: 'info',
5387
+ sessionId: response.sessionId,
5388
+ observationId: response.observationId,
5389
+ message: `A opção governada foi selecionada, mas o plano retornado não mira ${this.humanizeField(field)}.`,
5390
+ warnings: [
5391
+ ...(response.warnings ?? []),
5392
+ 'governed-categorical-selection-target-mismatch',
5393
+ ],
5394
+ };
5395
+ }
5396
+ const label = this.stringValue(request.action?.displayPrompt)
5397
+ || this.stringValue(request.action?.value)
5398
+ || this.stringValue(request.prompt)
5399
+ || 'opção governada';
5400
+ const target = field ? this.humanizeField(field) : 'a coluna categórica';
5401
+ const isNeutralFallback = this.normalizeLabel(label).includes('neutro');
5402
+ const booleanPlanBlocked = this.governedCategoricalBooleanPlanBoundary(response, request, field, isNeutralFallback);
5403
+ if (booleanPlanBlocked)
5404
+ return booleanPlanBlocked;
5405
+ if (!isNeutralFallback && !this.responseHasBooleanStatusNarrative(response))
5406
+ return response;
5407
+ const normalizedExplanation = isNeutralFallback
5408
+ ? `Vou aplicar chips neutros em ${target}, sem política por valor.`
5409
+ : `Vou aplicar a opção governada em ${target}.`;
5410
+ return {
5411
+ ...response,
5412
+ explanation: normalizedExplanation,
5413
+ message: undefined,
5414
+ componentEditPlan: undefined,
5415
+ warnings: [
5416
+ ...(response.warnings ?? []),
5417
+ 'governed-categorical-selection-review-text-normalized',
5418
+ isNeutralFallback
5419
+ ? 'The executable plan was preserved, but the neutral categorical guided option explanation was kept as the human review text instead of a generic renderer summary.'
5420
+ : 'The executable plan was preserved, but a stale boolean Status/Ativo/Inativo narrative was replaced because the selected guided option targets categorical presentation semantics.',
5421
+ ],
5422
+ };
5423
+ }
5424
+ governedCategoricalBooleanPlanBoundary(response, request, field, isNeutralFallback) {
5425
+ if (!field)
5426
+ return null;
5427
+ const column = this.currentConfigColumns()
5428
+ .find((candidate) => this.stringValue(candidate['field']) === field);
5429
+ if (!column || this.booleanColumnCandidate(column))
5430
+ return null;
5431
+ const hasBooleanOperations = this.responseHasBooleanStatusOperations(response);
5432
+ if (!hasBooleanOperations && (isNeutralFallback || !this.responseHasBooleanStatusNarrative(response))) {
5433
+ return null;
5434
+ }
5435
+ const label = this.stringValue(column['header']) || this.humanizeField(field);
5436
+ return {
5437
+ type: 'clarification',
5438
+ sessionId: response.sessionId ?? request.sessionId,
5439
+ observationId: response.observationId ?? request.observationId ?? null,
5440
+ message: `A coluna ${label} não é booleana no estado atual. Mantive a opção governada sem aplicação para não materializar Ativo/Inativo.`,
5441
+ questions: [`Como quer materializar ${label} agora?`],
5442
+ optionPayloads: [
5443
+ this.categoricalPresentationOptionPayload(field, label, 'author_categorical_field_semantics', `Descobrir semântica dos valores de ${label}`, 'Gerar chips/ícones por valor real da coluna.', 'auto_awesome', 'primary'),
5444
+ this.categoricalPresentationOptionPayload(field, label, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${label}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
5445
+ ],
5446
+ warnings: [
5447
+ ...(response.warnings ?? []),
5448
+ 'governed-categorical-boolean-plan-blocked',
5449
+ 'The selected governed categorical option returned boolean Status/Ativo/Inativo semantics for a non-boolean field; the executable plan was blocked.',
5450
+ ],
5451
+ };
5452
+ }
5453
+ requestHasGovernedCategoricalSelection(request) {
5454
+ const actionHints = this.toRecord(request.action?.contextHints);
5455
+ const requestHints = this.toRecord(request.contextHints);
5456
+ const actionText = this.normalizeLabel([
5457
+ request.action?.displayPrompt,
5458
+ request.action?.value,
5459
+ request.prompt,
5460
+ ].map((value) => this.stringValue(value)).join(' '));
5461
+ const actionRawText = [
5462
+ request.action?.displayPrompt,
5463
+ request.action?.value,
5464
+ request.prompt,
5465
+ ].map((value) => this.stringValue(value)).join(' ');
5466
+ return this.isGovernedCategoricalSemanticsChoiceText(actionText)
5467
+ || actionRawText.includes('apply_neutral_categorical_renderer')
5468
+ || actionRawText.includes('author_categorical_field_semantics')
5469
+ || !!this.toRecord(actionHints?.['categoricalFieldSemantics'])
5470
+ || !!this.toRecord(requestHints?.['categoricalFieldSemantics'])
5471
+ || this.governanceStatusIsNeutralCategoricalFallback(this.toRecord(actionHints?.['badge'])?.['governanceStatus'])
5472
+ || this.governanceStatusIsNeutralCategoricalFallback(this.toRecord(requestHints?.['badge'])?.['governanceStatus']);
5473
+ }
5474
+ requestSelectsCategoricalFieldSemanticsDiscovery(request) {
5475
+ const actionHints = this.toRecord(request.action?.contextHints);
5476
+ const requestHints = this.toRecord(request.contextHints);
5477
+ const actionSelection = this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection']);
5478
+ const requestSelection = this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
5479
+ const text = [
5480
+ request.action?.displayPrompt,
5481
+ request.action?.value,
5482
+ request.prompt,
5483
+ actionSelection?.['value'],
5484
+ requestSelection?.['value'],
5485
+ ].map((value) => this.stringValue(value)).join(' ');
5486
+ return text.includes('author_categorical_field_semantics');
5487
+ }
5488
+ governanceStatusIsNeutralCategoricalFallback(value) {
5489
+ const raw = this.stringValue(value);
5490
+ const normalized = this.normalizeLabel(raw);
5491
+ return normalized.includes('ungoverned neutral fallback')
5492
+ || raw.includes('ungoverned_neutral_fallback');
5493
+ }
5494
+ governedCategoricalSelectionTargetField(request, response) {
5495
+ const actionHints = this.toRecord(request.action?.contextHints);
5496
+ const requestHints = this.toRecord(request.contextHints);
5497
+ const actionSemantics = this.toRecord(actionHints?.['categoricalFieldSemantics']);
5498
+ const requestSemantics = this.toRecord(requestHints?.['categoricalFieldSemantics']);
5499
+ return this.stringValue(actionSemantics?.['field'])
5500
+ || this.stringValue(requestSemantics?.['field'])
5501
+ || this.visualPresentationTargetField(response, request);
5502
+ }
5503
+ responseHasBooleanStatusNarrative(response) {
5504
+ const text = this.normalizeLabel([
5505
+ response.message,
5506
+ response.explanation,
5507
+ ].map((value) => this.stringValue(value)).join(' '));
5508
+ return this.normalizedTextIncludesAny(text, [
5509
+ 'ativo para verdadeiro',
5510
+ 'inativo para falso',
5511
+ 'status como badge ativo',
5512
+ 'status como badge inativo',
5513
+ ]);
5514
+ }
5515
+ incompatibleBooleanStatusPresentationBoundary(response, request) {
5516
+ if (!request)
5517
+ return null;
5518
+ if (this.requestIsCanonicalRendererSelection(request))
5519
+ return null;
5520
+ if (this.requestHasGovernedCategoricalSelection(request))
5521
+ return null;
5522
+ if (this.responseCarriesClarificationChoices(response))
5523
+ return null;
5524
+ if (!this.promptRequestsVisualOrStructuralEdit(this.normalizeLabel(this.componentPresentationPromptForTurn(request)))) {
5525
+ return null;
5526
+ }
5527
+ const columns = this.currentConfigColumns();
5528
+ if (!columns.length)
5529
+ return null;
5530
+ const targetField = this.booleanStatusPresentationTargetField(response, request, columns);
5531
+ if (!targetField)
5532
+ return null;
5533
+ const targetColumn = columns.find((column) => this.stringValue(column['field']) === targetField);
5534
+ if (!targetColumn || this.booleanColumnCandidate(targetColumn))
5535
+ return null;
5536
+ if (!this.responseHasBooleanStatusNarrative(response) && !this.responseHasBooleanStatusOperations(response)) {
5537
+ return null;
5538
+ }
5539
+ const label = this.stringValue(targetColumn['header']) || this.humanizeField(targetField);
5540
+ return {
5541
+ type: 'clarification',
5542
+ sessionId: response.sessionId ?? request.sessionId,
5543
+ observationId: response.observationId ?? request.observationId ?? null,
5544
+ message: `A coluna ${label} não é booleana no estado atual. Posso formatá-la como categoria, mas não vou assumir Ativo/Inativo.`,
5545
+ questions: [`Qual apresentação categórica você quer aplicar em ${label}?`],
5546
+ optionPayloads: [
5547
+ this.categoricalPresentationOptionPayload(targetField, label, 'author_categorical_field_semantics', `Descobrir semântica dos valores de ${label}`, 'Gerar chips/ícones por valor real da coluna.', 'auto_awesome', 'primary'),
5548
+ this.categoricalPresentationOptionPayload(targetField, label, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${label}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
5549
+ ],
5550
+ warnings: [
5551
+ ...(response.warnings ?? []),
5552
+ 'boolean-status-presentation-blocked-for-categorical-column',
5553
+ 'The LLM returned boolean Status/Ativo/Inativo presentation semantics, but the current table column is not boolean; the table runtime kept the turn as governed categorical clarification instead of compiling an incompatible patch.',
5554
+ ],
5555
+ };
5556
+ }
5557
+ currentConfigColumns() {
5558
+ const currentConfig = this.adapter.getCurrentConfig?.();
5559
+ return Array.isArray(currentConfig?.['columns'])
5560
+ ? currentConfig['columns']
5561
+ .map((column) => this.toRecord(column))
5562
+ .filter((column) => !!column && !!this.stringValue(column['field']))
5563
+ : [];
5564
+ }
5565
+ currentConfigRows() {
5566
+ const currentConfig = this.adapter.getCurrentConfig?.();
5567
+ for (const source of [currentConfig]) {
5568
+ for (const key of ['data', 'rows', 'items']) {
5569
+ const rows = this.toArray(source?.[key])
5570
+ .map((row) => this.toRecord(row))
5571
+ .filter((row) => !!row);
5572
+ if (rows.length)
5573
+ return rows;
5574
+ }
5575
+ const dataSourceRows = this.toArray(this.toRecord(source?.['dataSource'])?.['data'])
5576
+ .map((row) => this.toRecord(row))
5577
+ .filter((row) => !!row);
5578
+ if (dataSourceRows.length)
5579
+ return dataSourceRows;
5580
+ }
5581
+ return [];
5582
+ }
5583
+ currentCategoricalValuesForField(field) {
5584
+ const scopedSamples = this.adapterFieldValueSamples(field);
5585
+ if (scopedSamples.length)
5586
+ return scopedSamples;
5587
+ const values = this.currentConfigRows()
5588
+ .map((row) => this.readableCellValue(row[field]))
5589
+ .filter((value) => !!value);
5590
+ return Array.from(new Set(values)).slice(0, 12);
5591
+ }
5592
+ adapterFieldValueSamples(field) {
5593
+ const adapter = this.adapter;
5594
+ const result = adapter.getFieldValueSamples?.(field, 12);
5595
+ const rawValues = Array.isArray(result)
5596
+ ? result
5597
+ : this.toArray(this.toRecord(result)?.['values']);
5598
+ const values = rawValues
5599
+ .map((value) => this.readableCellValue(value))
5600
+ .filter((value) => !!value);
5601
+ return Array.from(new Set(values)).slice(0, 12);
5602
+ }
5603
+ booleanStatusPresentationTargetField(response, request, columns) {
5604
+ const operationFields = this.componentEditOperations(response.componentEditPlan)
5605
+ .map((operation) => this.operationTargetField(operation))
5606
+ .filter((field) => !!field);
5607
+ const patch = this.toRecord(response.patch);
5608
+ const patchFields = this.toArray(patch?.['columns'])
5609
+ .map((column) => this.stringValue(this.toRecord(column)?.['field']))
5610
+ .filter((field) => !!field);
5611
+ const fields = [...operationFields, ...patchFields];
5612
+ for (const field of fields) {
5613
+ if (columns.some((column) => this.stringValue(column['field']) === field))
5614
+ return field;
5615
+ }
5616
+ return this.explicitVisualPresentationTargetField(response, request, columns);
5617
+ }
5618
+ explicitVisualPresentationTargetField(response, request, columns) {
5619
+ const text = this.normalizeLabel([
5620
+ request.prompt,
5621
+ request.pendingClarification?.sourcePrompt,
5622
+ request.pendingClarification?.assistantMessage,
5623
+ response.message,
5624
+ response.explanation,
5625
+ ...(response.questions ?? []),
5626
+ ].filter(Boolean).join(' '));
5627
+ const matched = columns
5628
+ .map((column) => {
5629
+ const field = this.stringValue(column['field']);
5630
+ if (!field)
5631
+ return null;
5632
+ const header = this.stringValue(column['header']) || field;
5633
+ const score = Math.max(this.visualPresentationTargetFieldScore(text, field), this.visualPresentationTargetFieldScore(text, header));
5634
+ return score > 0 ? { field, score } : null;
5635
+ })
5636
+ .filter((entry) => !!entry)
5637
+ .sort((a, b) => b.score - a.score)[0];
5638
+ return matched?.field ?? null;
5639
+ }
5640
+ responseHasBooleanStatusOperations(response) {
5641
+ const hasBooleanOperation = this.componentEditOperations(response.componentEditPlan).some((operation) => {
5642
+ const operationId = this.stringValue(operation['operationId']);
5643
+ if (operationId !== 'column.conditionalRenderer.add'
5644
+ && operationId !== 'column.conditionalStyle.add'
5645
+ && operationId !== 'column.renderer.set') {
5646
+ return false;
5647
+ }
5648
+ const input = this.toRecord(operation['input']);
5649
+ if (!input)
5650
+ return false;
5651
+ return this.booleanConditionValue(input['condition']) !== null
5652
+ || this.safeJsonText(input).includes('"Ativo"')
5653
+ || this.safeJsonText(input).includes('"Inativo"');
5654
+ });
5655
+ if (hasBooleanOperation)
5656
+ return true;
5657
+ const patchText = this.safeJsonText(response.patch);
5658
+ return patchText.includes('"Ativo"')
5659
+ || patchText.includes('"Inativo"')
5660
+ || patchText.includes('"true"')
5661
+ || patchText.includes('"false"');
5662
+ }
5663
+ categoricalPresentationOptionPayload(field, label, value, optionLabel, description, icon, tone) {
5664
+ return {
5665
+ label: optionLabel,
5666
+ value,
5667
+ description,
5668
+ contextHints: {
5669
+ categoricalFieldSemantics: {
5670
+ field,
5671
+ label,
5672
+ },
5673
+ optionSelected: {
5674
+ targetField: field,
5675
+ selection: {
5676
+ mode: value === 'author_categorical_field_semantics'
5677
+ ? 'categorical_semantics'
5678
+ : 'renderer',
5679
+ field,
5680
+ value,
5681
+ },
5682
+ },
5683
+ },
5684
+ presentation: {
5685
+ kind: 'guided-option',
5686
+ icon,
5687
+ tone,
5688
+ ctaLabel: 'Aplicar opção',
5689
+ },
5690
+ };
5691
+ }
5692
+ visualPresentationTargetMismatchBoundary(response, request) {
5693
+ if (!request)
5694
+ return null;
5695
+ if (this.requestIsCanonicalRendererSelection(request))
5696
+ return null;
5697
+ if (this.requestHasGovernedCategoricalSelection(request))
5698
+ return null;
5699
+ if (this.responseCarriesClarificationChoices(response))
5700
+ return null;
5701
+ if (!this.promptRequestsVisualOrStructuralEdit(this.normalizeLabel(this.componentPresentationPromptForTurn(request)))) {
5702
+ return null;
5703
+ }
5704
+ const columns = this.currentConfigColumns();
5705
+ if (!columns.length)
5706
+ return null;
5707
+ const requestedField = this.explicitVisualPresentationTargetFieldFromRequest(request, columns);
5708
+ if (!requestedField)
5709
+ return null;
5710
+ const responseFields = this.responsePresentationTargetFields(response, columns);
5711
+ if (!responseFields.length)
5712
+ return null;
5713
+ if (responseFields.some((field) => field === requestedField))
5714
+ return null;
5715
+ const requestedColumn = columns.find((column) => this.stringValue(column['field']) === requestedField);
5716
+ const requestedLabel = this.stringValue(requestedColumn?.['header']) || this.humanizeField(requestedField);
5717
+ return {
5718
+ type: 'clarification',
5719
+ sessionId: response.sessionId ?? request.sessionId,
5720
+ observationId: response.observationId ?? request.observationId ?? null,
5721
+ message: `Entendi que o alvo do ajuste é ${requestedLabel}. Para evitar aplicar uma formatação em outra coluna, vou seguir com uma decisão categórica segura para ${requestedLabel}.`,
5722
+ questions: [`Qual apresentação categórica você quer aplicar em ${requestedLabel}?`],
5723
+ optionPayloads: [
5724
+ this.categoricalPresentationOptionPayload(requestedField, requestedLabel, 'author_categorical_field_semantics', `Descobrir semântica dos valores de ${requestedLabel}`, 'Gerar chips/ícones por valor real da coluna.', 'auto_awesome', 'primary'),
5725
+ this.categoricalPresentationOptionPayload(requestedField, requestedLabel, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${requestedLabel}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
5726
+ ],
5727
+ warnings: [
5728
+ ...(response.warnings ?? []),
5729
+ 'visual-presentation-target-mismatch-blocked',
5730
+ 'The LLM returned an executable visual presentation plan for a different column than the explicit user target; the table runtime kept the turn non-applicable instead of compiling a patch for the wrong column.',
5731
+ ],
5732
+ };
5733
+ }
5734
+ explicitVisualPresentationTargetFieldFromRequest(request, columns) {
5735
+ const text = this.normalizeLabel([
5736
+ request.prompt,
5737
+ request.action?.displayPrompt,
5738
+ request.pendingClarification?.sourcePrompt,
5739
+ ].filter(Boolean).join(' '));
5740
+ return this.explicitColumnMentionFromText(text, columns);
5741
+ }
5742
+ explicitColumnMentionFromText(normalizedText, columns) {
5743
+ const matched = columns
5744
+ .map((column) => {
5745
+ const field = this.stringValue(column['field']);
5746
+ if (!field)
5747
+ return null;
5748
+ const header = this.stringValue(column['header']) || field;
5749
+ const score = Math.max(this.visualPresentationTargetFieldScore(normalizedText, field), this.visualPresentationTargetFieldScore(normalizedText, header));
5750
+ return score > 0 ? { field, score } : null;
5751
+ })
5752
+ .filter((entry) => !!entry)
5753
+ .sort((a, b) => b.score - a.score);
5754
+ const mentionedFields = [...new Set(matched.map((entry) => entry.field))];
5755
+ return mentionedFields.length === 1 ? mentionedFields[0] : null;
5756
+ }
5757
+ responsePresentationTargetFields(response, columns) {
5758
+ const operationFields = this.componentEditOperations(response.componentEditPlan)
5759
+ .filter((operation) => this.operationIsColumnPresentationEdit(operation))
5760
+ .map((operation) => this.operationTargetField(operation));
5761
+ const patch = this.toRecord(response.patch);
5762
+ const patchFields = this.toArray(patch?.['columns'])
5763
+ .map((column) => this.toRecord(column))
5764
+ .filter((column) => !!column)
5765
+ .filter((column) => this.patchColumnCarriesPresentationEdit(column))
5766
+ .map((column) => this.stringValue(column['field']));
5767
+ const knownFields = new Set(columns.map((column) => this.stringValue(column['field'])).filter((field) => !!field));
5768
+ return [...new Set([...operationFields, ...patchFields].filter((field) => knownFields.has(field)))];
5769
+ }
5770
+ operationIsColumnPresentationEdit(operation) {
5771
+ const operationId = this.stringValue(operation['operationId']);
5772
+ return [
5773
+ 'column.renderer.set',
5774
+ 'column.conditionalRenderer.add',
5775
+ 'column.conditionalStyle.add',
5776
+ 'column.format.set',
5777
+ 'column.alignment.set',
5778
+ ].includes(operationId);
5779
+ }
5780
+ patchColumnCarriesPresentationEdit(column) {
5781
+ return !!this.toRecord(column['renderer'])
5782
+ || Array.isArray(column['conditionalRenderers'])
5783
+ || Array.isArray(column['conditionalStyles'])
5784
+ || !!this.stringValue(column['format'])
5785
+ || !!this.stringValue(column['align'])
5786
+ || !!this.stringValue(column['alignment']);
5787
+ }
5788
+ normalizeNumericBandConditionalStylePlan(response, request) {
5789
+ if (!request)
5790
+ return response;
5791
+ if ((response.warnings ?? []).includes('numeric-band-conditional-style-normalized'))
5792
+ return response;
5793
+ const currentConfig = this.adapter.getCurrentConfig?.();
5794
+ const columns = Array.isArray(currentConfig?.['columns'])
5795
+ ? currentConfig['columns']
5796
+ .map((column) => this.toRecord(column))
5797
+ .filter((column) => !!column && !!this.stringValue(column['field']))
5798
+ : [];
5799
+ if (!columns.length)
5800
+ return response;
5801
+ const operations = this.numericBandConditionalStyleOperationsFromPrompt(this.componentPresentationPromptForTurn(request), [
5802
+ response.message,
5803
+ response.explanation,
5804
+ ...(response.questions ?? []),
5805
+ ].join(' '), columns);
5806
+ if (!operations.length)
5807
+ return response;
5808
+ const fields = new Set(operations
5809
+ .map((operation) => this.operationTargetField(operation))
5810
+ .filter((field) => !!field));
5811
+ const originalPlan = this.toRecord(response.componentEditPlan);
5812
+ const originalOperations = this.componentEditOperations(originalPlan)
5813
+ .filter((operation) => {
5814
+ const field = this.operationTargetField(operation);
5815
+ return !(field && fields.has(field) && this.stringValue(operation['operationId']) === 'column.conditionalStyle.add');
5816
+ });
5817
+ return {
5818
+ ...response,
5819
+ ...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
5820
+ componentEditPlan: {
5821
+ ...(originalPlan ?? {}),
5822
+ kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
5823
+ version: this.stringValue(originalPlan?.['version']) || '1.0',
5824
+ componentId: this.stringValue(originalPlan?.['componentId'])
5825
+ || this.adapter.componentId
5826
+ || request.componentId
5827
+ || 'praxis-table',
5828
+ operations: [...originalOperations, ...operations],
5829
+ },
5830
+ explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
5831
+ warnings: [
5832
+ ...(response.warnings ?? []),
5833
+ 'numeric-band-conditional-style-normalized',
5834
+ 'Residual continuity guard grounded numeric band thresholds from the canonical dataProfile when available, avoiding accidental thresholds from prose or visual token numbers.',
5835
+ ],
5836
+ };
5837
+ }
5838
+ componentEditPlanHasAnyOperation(componentEditPlan, operationIds) {
5839
+ if (!operationIds.length)
5840
+ return false;
5841
+ const expected = new Set(operationIds);
5842
+ return this.componentEditOperations(componentEditPlan)
5843
+ .some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
5844
+ }
5845
+ responseHasComputedColumnAuthoringDecision(response) {
5846
+ if (this.componentEditPlanHasAnyOperation(response.componentEditPlan, ['column.computed.add', 'column.computed.set'])) {
5847
+ return true;
5848
+ }
5849
+ const patch = this.toRecord(response.patch);
5850
+ const columns = Array.isArray(patch?.['columns']) ? patch['columns'] : [];
5851
+ return columns
5852
+ .map((column) => this.toRecord(column))
5853
+ .some((column) => !!column?.['computed']);
5854
+ }
5855
+ normalizeComputedColumnAuxiliaryMaterializations(response) {
5856
+ const plan = this.toRecord(response.componentEditPlan);
5857
+ const operations = this.componentEditOperations(plan);
5858
+ if (!plan || operations.length < 2)
5859
+ return response;
5860
+ const computedFields = new Set(operations
5861
+ .filter((operation) => this.componentEditPlanOperationIdentity(operation) === 'column.computed.add'
5862
+ || this.componentEditPlanOperationIdentity(operation) === 'column.computed.set')
5863
+ .map((operation) => this.operationTargetField(operation))
5864
+ .filter((field) => !!field));
5865
+ if (!computedFields.size) {
5866
+ return response;
5867
+ }
5868
+ const auxiliaryAddedFields = new Set();
5869
+ for (const operation of operations) {
5870
+ const operationId = this.componentEditPlanOperationIdentity(operation);
5871
+ const field = this.operationTargetField(operation);
5872
+ if (!field)
5873
+ continue;
5874
+ if (operationId === 'column.add' && !computedFields.has(field)) {
5023
5875
  auxiliaryAddedFields.add(field);
5024
5876
  }
5025
5877
  }
@@ -5195,18 +6047,7 @@ class TableAgenticAuthoringTurnFlow {
5195
6047
  .sort((left, right) => left - right)[0] ?? -1;
5196
6048
  }
5197
6049
  columnPresentationAliases(column) {
5198
- const aliases = new Set(this.columnMentionAliases(column));
5199
- const field = this.normalizeLabel(this.stringValue(column['field']));
5200
- const header = this.normalizeLabel(this.stringValue(column['header']));
5201
- if (field.startsWith('nome') || header.includes('nome'))
5202
- aliases.add('nome');
5203
- if (field.includes('funcionario') || header.includes('funcionario'))
5204
- aliases.add('nome');
5205
- if (field.includes('salario') || header.includes('salario'))
5206
- aliases.add('salario');
5207
- if (field.includes('salario') || header.includes('salario'))
5208
- aliases.add('salário');
5209
- return [...aliases].sort((left, right) => right.length - left.length);
6050
+ return this.columnMentionAliases(column);
5210
6051
  }
5211
6052
  mergeCompoundColumnPresentationOperations(originalOperations, intendedOperations) {
5212
6053
  const protectedFields = new Map();
@@ -5306,15 +6147,8 @@ class TableAgenticAuthoringTurnFlow {
5306
6147
  return null;
5307
6148
  }
5308
6149
  const column = columns.find((candidate) => {
5309
- const text = this.normalizeLabel([
5310
- candidate['field'],
5311
- candidate['header'],
5312
- candidate['type'],
5313
- candidate['dataType'],
5314
- this.humanizeField(this.stringValue(candidate['field'])),
5315
- ].map((value) => this.stringValue(value)).join(' '));
5316
6150
  return (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate))
5317
- && this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool']);
6151
+ && this.booleanColumnCandidate(candidate);
5318
6152
  });
5319
6153
  return this.booleanSimNaoOperationsForColumn(column);
5320
6154
  }
@@ -5349,7 +6183,8 @@ class TableAgenticAuthoringTurnFlow {
5349
6183
  candidate['dataType'],
5350
6184
  this.humanizeField(this.stringValue(candidate['field'])),
5351
6185
  ].map((value) => this.stringValue(value)).join(' '));
5352
- return this.normalizedTextIncludesAny(text, ['status', 'ativo', 'boolean', 'bool']);
6186
+ return this.booleanColumnCandidate(candidate)
6187
+ && this.normalizedTextIncludesAny(text, ['status', 'ativo', 'boolean', 'bool']);
5353
6188
  });
5354
6189
  const field = this.stringValue(column?.['field']);
5355
6190
  if (!field)
@@ -5488,28 +6323,20 @@ class TableAgenticAuthoringTurnFlow {
5488
6323
  ]);
5489
6324
  }
5490
6325
  numericColumnCandidate(column) {
5491
- const text = this.normalizeLabel([
5492
- column['field'],
5493
- column['header'],
6326
+ const typeText = this.normalizeLabel([
5494
6327
  column['type'],
5495
6328
  column['dataType'],
5496
6329
  column['format'],
5497
- this.humanizeField(this.stringValue(column['field'])),
5498
6330
  ].map((value) => this.stringValue(value)).join(' '));
5499
- return this.normalizedTextIncludesAny(text, [
6331
+ return this.normalizedTextIncludesAny(typeText, [
5500
6332
  'number',
5501
6333
  'numeric',
5502
6334
  'decimal',
5503
6335
  'integer',
5504
6336
  'currency',
5505
6337
  'moeda',
5506
- 'valor',
5507
- 'preco',
5508
- 'preço',
5509
- 'salario',
5510
- 'salário',
5511
6338
  'brl',
5512
- ]);
6339
+ ]) || this.numericBandThresholdsFromDataProfile(this.stringValue(column['field'])) !== null;
5513
6340
  }
5514
6341
  numericBandThresholdsForField(field, text) {
5515
6342
  return this.numericBandThresholdsFromDataProfile(field)
@@ -5613,14 +6440,7 @@ class TableAgenticAuthoringTurnFlow {
5613
6440
  return null;
5614
6441
  }
5615
6442
  const booleanColumns = columns.filter((candidate) => {
5616
- const text = this.normalizeLabel([
5617
- candidate['field'],
5618
- candidate['header'],
5619
- candidate['type'],
5620
- candidate['dataType'],
5621
- this.humanizeField(this.stringValue(candidate['field'])),
5622
- ].map((value) => this.stringValue(value)).join(' '));
5623
- return this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool'])
6443
+ return this.booleanColumnCandidate(candidate)
5624
6444
  && (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate));
5625
6445
  });
5626
6446
  return this.booleanSimNaoOperationsForColumn(booleanColumns[0]);
@@ -5632,16 +6452,7 @@ class TableAgenticAuthoringTurnFlow {
5632
6452
  || this.normalizedTextIncludesAny(normalizedResponse, ['sim nao', 'sim não', 'sim ou nao', 'sim ou não']);
5633
6453
  if (!simNaoRequested)
5634
6454
  return null;
5635
- const booleanColumns = columns.filter((candidate) => {
5636
- const text = this.normalizeLabel([
5637
- candidate['field'],
5638
- candidate['header'],
5639
- candidate['type'],
5640
- candidate['dataType'],
5641
- this.humanizeField(this.stringValue(candidate['field'])),
5642
- ].map((value) => this.stringValue(value)).join(' '));
5643
- return this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool']);
5644
- });
6455
+ const booleanColumns = columns.filter((candidate) => this.booleanColumnCandidate(candidate));
5645
6456
  return booleanColumns.length === 1
5646
6457
  ? this.booleanSimNaoOperationsForColumn(booleanColumns[0])
5647
6458
  : null;
@@ -5780,18 +6591,21 @@ class TableAgenticAuthoringTurnFlow {
5780
6591
  }
5781
6592
  inferSpecificColumnFormatFromPrompt(prompt, column, availableFormats) {
5782
6593
  const normalizedPrompt = this.normalizeLabel(prompt);
5783
- const columnText = this.normalizeLabel([
5784
- column['field'],
5785
- column['header'],
6594
+ const columnTypeText = this.normalizeLabel([
5786
6595
  column['type'],
5787
6596
  column['dataType'],
5788
- this.humanizeField(this.stringValue(column['field'])),
6597
+ column['format'],
5789
6598
  ].map((value) => this.stringValue(value)).filter(Boolean).join(' '));
5790
- const dateLikeColumn = this.normalizedTextIncludesAny(columnText, [
6599
+ const dateLikeColumn = this.normalizedTextIncludesAny(columnTypeText, [
5791
6600
  'data',
5792
6601
  'date',
5793
- 'admissao',
5794
- 'admissão',
6602
+ 'time',
6603
+ 'dd/mm/yyyy',
6604
+ 'yyyy-mm-dd',
6605
+ 'shortdate',
6606
+ 'mediumdate',
6607
+ 'longdate',
6608
+ 'fulldate',
5795
6609
  ]);
5796
6610
  const brDateRequested = this.normalizedTextIncludesAny(normalizedPrompt, [
5797
6611
  'dia mes ano',
@@ -5805,15 +6619,7 @@ class TableAgenticAuthoringTurnFlow {
5805
6619
  if (dateLikeColumn && brDateRequested && availableFormats.has('dd/MM/yyyy')) {
5806
6620
  return 'dd/MM/yyyy';
5807
6621
  }
5808
- const currencyLikeColumn = this.normalizedTextIncludesAny(columnText, [
5809
- 'salario',
5810
- 'salário',
5811
- 'salary',
5812
- 'remuneracao',
5813
- 'remuneração',
5814
- 'currency',
5815
- 'moeda',
5816
- ]);
6622
+ const currencyLikeColumn = this.numericColumnCandidate(column);
5817
6623
  const brlRequested = this.normalizedTextIncludesAny(normalizedPrompt, [
5818
6624
  'real',
5819
6625
  'reais',
@@ -6262,8 +7068,6 @@ class TableAgenticAuthoringTurnFlow {
6262
7068
  return null;
6263
7069
  if (!response.patch || this.tableFilterApplyOperations(response.patch).length === 0)
6264
7070
  return null;
6265
- if (this.responseDeclaresTableMutation(response))
6266
- return null;
6267
7071
  const contextHints = this.contextHintsFor(request);
6268
7072
  return {
6269
7073
  type: 'info',
@@ -6276,7 +7080,28 @@ class TableAgenticAuthoringTurnFlow {
6276
7080
  ],
6277
7081
  };
6278
7082
  }
6279
- selectedRecordReadonlyClarificationBoundaryResponse(response, request) {
7083
+ selectedRecordReadonlyClarificationBoundaryResponse(response, request) {
7084
+ if (!this.selectedRecordReadOnlyAnalysisRequested(request))
7085
+ return null;
7086
+ if (response.patch)
7087
+ return null;
7088
+ const contextHints = this.contextHintsFor(request);
7089
+ if (!this.selectedRecordSampleRows(contextHints).length)
7090
+ return null;
7091
+ if (!this.responseAsksForSelectedRecordAnalysisDimension(response))
7092
+ return null;
7093
+ return {
7094
+ type: 'info',
7095
+ sessionId: response.sessionId ?? request?.sessionId,
7096
+ message: this.selectedRecordsAnalyticalSummary(contextHints),
7097
+ warnings: [
7098
+ ...(response.warnings ?? []),
7099
+ 'selected-record-readonly-analysis-clarification-collapsed',
7100
+ 'A plataforma respondeu a analise read-only usando selectedRecordsContext.sampleRows em vez de devolver uma clarificacao generica de campo.',
7101
+ ],
7102
+ };
7103
+ }
7104
+ selectedRecordReadonlyPresentationBoundaryResponse(response, request) {
6280
7105
  if (!this.selectedRecordReadOnlyAnalysisRequested(request))
6281
7106
  return null;
6282
7107
  if (response.patch)
@@ -6284,7 +7109,10 @@ class TableAgenticAuthoringTurnFlow {
6284
7109
  const contextHints = this.contextHintsFor(request);
6285
7110
  if (!this.selectedRecordSampleRows(contextHints).length)
6286
7111
  return null;
6287
- if (!this.responseAsksForSelectedRecordAnalysisDimension(response))
7112
+ const hasPresentationOptions = (response.optionPayloads ?? [])
7113
+ .some((payload) => this.isVisualPresentationClarificationPayload(payload)
7114
+ || this.isGovernedCategoricalSemanticsChoicePayload(payload));
7115
+ if (!hasPresentationOptions && !this.responseHasGovernedCategoricalSemanticsChoice(response))
6288
7116
  return null;
6289
7117
  return {
6290
7118
  type: 'info',
@@ -6292,8 +7120,8 @@ class TableAgenticAuthoringTurnFlow {
6292
7120
  message: this.selectedRecordsAnalyticalSummary(contextHints),
6293
7121
  warnings: [
6294
7122
  ...(response.warnings ?? []),
6295
- 'selected-record-readonly-analysis-clarification-collapsed',
6296
- 'A plataforma respondeu a analise read-only usando selectedRecordsContext.sampleRows em vez de devolver uma clarificacao generica de campo.',
7123
+ 'selected-record-readonly-analysis-presentation-misroute-collapsed',
7124
+ 'A plataforma respondeu a analise read-only usando selectedRecordsContext.sampleRows porque a resposta semantica retornou opcoes de apresentacao visual para uma pergunta consultiva.',
6297
7125
  ],
6298
7126
  };
6299
7127
  }
@@ -6396,15 +7224,126 @@ class TableAgenticAuthoringTurnFlow {
6396
7224
  ]),
6397
7225
  ]
6398
7226
  .map((target) => this.normalizeLabel(this.stringValue(target)))
6399
- .filter((target) => target.length > 2);
7227
+ .filter((target) => target.length > 2)
7228
+ .filter((target) => !this.selectedRecordGenericCommonalityScopeTarget(target));
6400
7229
  return !fieldTargets.some((target) => this.normalizedTextHasStandaloneToken(normalizedPrompt, target)
6401
7230
  || this.normalizedTextContainsApproxPhrase(normalizedPrompt, target));
6402
7231
  }
7232
+ selectedRecordLocalReadonlyAnalysisRequested(request) {
7233
+ const contextHints = this.contextHintsFor(request);
7234
+ if (!this.selectedRecordSampleRows(contextHints).length)
7235
+ return false;
7236
+ if (this.toRecord(contextHints?.['selectedRecordsFilter'])
7237
+ || this.toRecord(request?.action?.contextHints?.['selectedRecordsFilter'])
7238
+ || this.toRecord(contextHints?.['tableRuntimeOperation'])
7239
+ || this.toRecord(request?.action?.contextHints?.['tableRuntimeOperation'])) {
7240
+ return false;
7241
+ }
7242
+ const normalizedPrompt = this.normalizeLabel(request?.prompt ?? '');
7243
+ if (!normalizedPrompt)
7244
+ return false;
7245
+ if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
7246
+ || this.promptRequestsExportOperation(normalizedPrompt)) {
7247
+ return this.promptExplicitlyForbidsTableMutation(normalizedPrompt);
7248
+ }
7249
+ if (this.promptRequestsSelectedRecordDerivedOperation(normalizedPrompt))
7250
+ return false;
7251
+ return this.promptExplicitlyForbidsTableMutation(normalizedPrompt)
7252
+ || this.selectedRecordPromptTargetsSelectedSet(normalizedPrompt);
7253
+ }
7254
+ selectedRecordPromptTargetsSelectedSet(normalizedPrompt) {
7255
+ if (!normalizedPrompt)
7256
+ return false;
7257
+ const selectedSetSignals = [
7258
+ 'selecionado',
7259
+ 'selecionados',
7260
+ 'marcado',
7261
+ 'marcados',
7262
+ 'linha marcada',
7263
+ 'linhas marcadas',
7264
+ 'registro marcado',
7265
+ 'registros marcados',
7266
+ 'registro selecionado',
7267
+ 'registros selecionados',
7268
+ 'esses',
7269
+ 'essas',
7270
+ 'nestes',
7271
+ 'nestas',
7272
+ 'nesses',
7273
+ 'nessas',
7274
+ 'desses',
7275
+ 'dessas',
7276
+ 'tres',
7277
+ 'três',
7278
+ '3',
7279
+ ];
7280
+ const analyticalSignals = [
7281
+ 'em comum',
7282
+ 'tem comum',
7283
+ 'igual',
7284
+ 'iguais',
7285
+ 'compare',
7286
+ 'comparar',
7287
+ 'resuma',
7288
+ 'resume',
7289
+ 'resumir',
7290
+ 'analise',
7291
+ 'analisar',
7292
+ 'padrao',
7293
+ 'padrão',
7294
+ 'fora do padrao',
7295
+ 'fora do padrão',
7296
+ 'ganha mais',
7297
+ 'ganha menos',
7298
+ 'quem ganha',
7299
+ 'faixa salarial',
7300
+ 'faixa de salario',
7301
+ 'faixa de salário',
7302
+ 'faixa de admissao',
7303
+ 'faixa de admissão',
7304
+ 'periodo de admissao',
7305
+ 'período de admissão',
7306
+ 'departamento',
7307
+ 'cargo',
7308
+ 'cargos',
7309
+ 'salario',
7310
+ 'salário',
7311
+ 'admissao',
7312
+ 'admissão',
7313
+ ];
7314
+ const hasSelectedScope = selectedSetSignals.some((signal) => this.normalizedTextHasStandaloneToken(normalizedPrompt, signal)
7315
+ || this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
7316
+ if (!hasSelectedScope)
7317
+ return false;
7318
+ return analyticalSignals.some((signal) => this.normalizedTextHasStandaloneToken(normalizedPrompt, signal)
7319
+ || this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
7320
+ }
7321
+ selectedRecordGenericCommonalityScopeTarget(normalizedTarget) {
7322
+ if (!normalizedTarget)
7323
+ return true;
7324
+ return [
7325
+ 'funcionario',
7326
+ 'funcionarios',
7327
+ 'colaborador',
7328
+ 'colaboradores',
7329
+ 'registro',
7330
+ 'registros',
7331
+ 'linha',
7332
+ 'linhas',
7333
+ 'selecionado',
7334
+ 'selecionados',
7335
+ 'selected',
7336
+ 'record',
7337
+ 'records',
7338
+ 'row',
7339
+ 'rows',
7340
+ ].some((scopeTerm) => this.normalizedTextHasStandaloneToken(normalizedTarget, scopeTerm)
7341
+ || normalizedTarget === scopeTerm);
7342
+ }
6403
7343
  selectedRecordPromptLooksConsultative(normalizedPrompt) {
6404
7344
  if (!normalizedPrompt)
6405
7345
  return false;
6406
- if (this.promptRequestsRuntimeFilter(normalizedPrompt)
6407
- || this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
7346
+ if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
6408
7347
  || this.promptRequestsExportOperation(normalizedPrompt)) {
6409
7348
  return this.promptExplicitlyForbidsTableMutation(normalizedPrompt);
6410
7349
  }
@@ -6451,6 +7390,30 @@ class TableAgenticAuthoringTurnFlow {
6451
7390
  // This ranks explicit selected-record operation wording after selectedRecordsContext is already available.
6452
7391
  // The primary intent still comes from the semantic turn/contract; these phrases only prevent a read-only
6453
7392
  // safety boundary from cancelling an operation the user explicitly asked to materialize.
7393
+ if (this.selectedRecordPromptLooksConsultative(normalizedPrompt)) {
7394
+ const explicitOperationSignals = [
7395
+ 'filtrar',
7396
+ 'filtra',
7397
+ 'filtro',
7398
+ 'filtre',
7399
+ 'buscar registros',
7400
+ 'procurar registros',
7401
+ 'acha quem',
7402
+ 'achar quem',
7403
+ 'outros registros',
7404
+ 'quero outros',
7405
+ 'quero os mesmo',
7406
+ 'quero os mesmos',
7407
+ 'quero ver gente',
7408
+ 'pega a turma',
7409
+ 'pegar a turma',
7410
+ ].map((signal) => this.normalizeLabel(signal));
7411
+ const explicitlyRequestsOperation = explicitOperationSignals.some((signal) => signal.includes(' ')
7412
+ ? this.normalizedTextContainsExactPhrase(normalizedPrompt, signal)
7413
+ : this.normalizedTextHasStandaloneToken(normalizedPrompt, signal));
7414
+ if (!explicitlyRequestsOperation)
7415
+ return false;
7416
+ }
6454
7417
  return [
6455
7418
  'filtrar',
6456
7419
  'filtra',
@@ -6458,6 +7421,10 @@ class TableAgenticAuthoringTurnFlow {
6458
7421
  'filtre',
6459
7422
  'buscar registros',
6460
7423
  'procurar registros',
7424
+ 'acha quem',
7425
+ 'achar quem',
7426
+ 'pega a turma',
7427
+ 'pegar a turma',
6461
7428
  'outros registros',
6462
7429
  'registro parecido',
6463
7430
  'registros parecidos',
@@ -6476,7 +7443,20 @@ class TableAgenticAuthoringTurnFlow {
6476
7443
  'quero os mesmo',
6477
7444
  'quero os mesmos',
6478
7445
  'quero ver gente',
6479
- ].some((signal) => this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
7446
+ 'mesma banda',
7447
+ 'nessa mesma banda',
7448
+ 'nessa epoca',
7449
+ 'nessa época',
7450
+ ].some((signal) => {
7451
+ const normalizedSignal = this.normalizeLabel(signal);
7452
+ if (!normalizedSignal)
7453
+ return false;
7454
+ if (normalizedSignal.includes(' ')) {
7455
+ return this.normalizedTextContainsExactPhrase(normalizedPrompt, normalizedSignal);
7456
+ }
7457
+ return this.normalizedTextHasStandaloneToken(normalizedPrompt, normalizedSignal)
7458
+ || this.normalizedTextContainsApproxToken(normalizedPrompt, normalizedSignal);
7459
+ });
6480
7460
  }
6481
7461
  promptExplicitlyForbidsTableMutation(normalizedPrompt) {
6482
7462
  if (!normalizedPrompt)
@@ -6566,16 +7546,17 @@ class TableAgenticAuthoringTurnFlow {
6566
7546
  .slice(0, 4);
6567
7547
  const dateRanges = fields
6568
7548
  .map((field) => {
6569
- const timestamps = rows
6570
- .map((row) => this.dateTimestamp(row[field]))
6571
- .filter((value) => value !== null);
6572
- if (timestamps.length < 2)
7549
+ const values = rows
7550
+ .map((row) => this.analysisDateValue(row[field]))
7551
+ .filter((value) => !!value);
7552
+ if (values.length < 2)
6573
7553
  return null;
6574
- const min = Math.min(...timestamps);
6575
- const max = Math.max(...timestamps);
6576
- if (min === max)
7554
+ const sorted = [...values].sort((left, right) => left.sort - right.sort);
7555
+ const min = sorted[0];
7556
+ const max = sorted[sorted.length - 1];
7557
+ if (!min || !max || min.sort === max.sort)
6577
7558
  return null;
6578
- return `${labelByField.get(field) || this.humanizeField(field)}: ${this.formatAnalysisDate(min)} até ${this.formatAnalysisDate(max)}`;
7559
+ return `${labelByField.get(field) || this.humanizeField(field)}: ${min.label} até ${max.label}`;
6579
7560
  })
6580
7561
  .filter((entry) => !!entry)
6581
7562
  .slice(0, 3);
@@ -6631,6 +7612,27 @@ class TableAgenticAuthoringTurnFlow {
6631
7612
  : Number.NaN;
6632
7613
  return Number.isFinite(raw) ? raw : null;
6633
7614
  }
7615
+ analysisDateValue(value) {
7616
+ if (typeof value === 'string') {
7617
+ const trimmed = value.trim();
7618
+ const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(trimmed);
7619
+ if (dateOnly) {
7620
+ const year = Number(dateOnly[1]);
7621
+ const month = Number(dateOnly[2]);
7622
+ const day = Number(dateOnly[3]);
7623
+ if (Number.isInteger(year) && Number.isInteger(month) && Number.isInteger(day)) {
7624
+ return {
7625
+ sort: year * 10000 + month * 100 + day,
7626
+ label: `${String(day).padStart(2, '0')}/${String(month).padStart(2, '0')}/${String(year)}`,
7627
+ };
7628
+ }
7629
+ }
7630
+ }
7631
+ const timestamp = this.dateTimestamp(value);
7632
+ return timestamp === null
7633
+ ? null
7634
+ : { sort: timestamp, label: this.formatAnalysisDate(timestamp) };
7635
+ }
6634
7636
  formatAnalysisDate(timestamp) {
6635
7637
  return new Intl.DateTimeFormat('pt-BR', {
6636
7638
  day: '2-digit',
@@ -6967,6 +7969,8 @@ class TableAgenticAuthoringTurnFlow {
6967
7969
  return operations;
6968
7970
  }
6969
7971
  requestCarriesVisualPresentationContext(request) {
7972
+ if (this.requestHasGovernedCategoricalSelection(request))
7973
+ return true;
6970
7974
  const actionHints = this.toRecord(request.action?.contextHints);
6971
7975
  const requestHints = this.toRecord(request.contextHints);
6972
7976
  const pendingDiagnostics = this.toRecord(request.pendingClarification?.diagnostics);
@@ -7449,7 +8453,10 @@ class TableAgenticAuthoringTurnFlow {
7449
8453
  add(header);
7450
8454
  add(field);
7451
8455
  add(this.humanizeField(field));
7452
- for (const source of [header, field, this.humanizeField(field)]) {
8456
+ for (const alias of this.stringArrayValue(column['aliases'])) {
8457
+ add(alias);
8458
+ }
8459
+ for (const source of [header, field, this.humanizeField(field), ...this.stringArrayValue(column['aliases'])]) {
7453
8460
  const normalized = this.normalizeLabel(source);
7454
8461
  const tokens = normalized.split(' ').filter((token) => token.length >= 3
7455
8462
  && !['nome', 'valor', 'data', 'campo', 'codigo', 'id'].includes(token));
@@ -7660,6 +8667,24 @@ class TableAgenticAuthoringTurnFlow {
7660
8667
  ...(memory ? { tableComponentEditDecision: memory } : {}),
7661
8668
  };
7662
8669
  }
8670
+ errorRetryDiagnostics(request, response) {
8671
+ const actionHints = this.toRecord(request.action?.contextHints);
8672
+ const retryableAction = request.action && actionHints
8673
+ ? {
8674
+ kind: request.action.kind || 'clarify',
8675
+ value: this.stringValue(request.action.value) || request.prompt || '',
8676
+ displayPrompt: this.stringValue(request.action.displayPrompt),
8677
+ contextHints: actionHints,
8678
+ }
8679
+ : null;
8680
+ const warnings = response.warnings?.filter(Boolean) ?? [];
8681
+ if (!retryableAction && !warnings.length)
8682
+ return undefined;
8683
+ return {
8684
+ ...(warnings.length ? { warnings } : {}),
8685
+ ...(retryableAction ? { retryAction: retryableAction } : {}),
8686
+ };
8687
+ }
7663
8688
  tableConversationMemoryHints(request) {
7664
8689
  const diagnostics = this.toRecord(request.diagnostics);
7665
8690
  const decision = this.toRecord(diagnostics?.['tableComponentEditDecision']);
@@ -7734,6 +8759,9 @@ class TableAgenticAuthoringTurnFlow {
7734
8759
  completePendingComponentEditClarification(request) {
7735
8760
  if (request.action?.kind !== 'clarify')
7736
8761
  return null;
8762
+ const computedColumnCreation = this.completePendingComputedColumnClarification(request);
8763
+ if (computedColumnCreation)
8764
+ return computedColumnCreation;
7737
8765
  const diagnostics = this.toRecord(request.pendingClarification?.diagnostics);
7738
8766
  const continuation = this.toRecord(diagnostics?.['tableComponentEditContinuation']);
7739
8767
  if (!continuation)
@@ -7762,6 +8790,127 @@ class TableAgenticAuthoringTurnFlow {
7762
8790
  explanation: 'Clarificacao aplicada ao ajuste pendente.',
7763
8791
  };
7764
8792
  }
8793
+ completePendingComputedColumnClarification(request) {
8794
+ const sourcePrompt = request.pendingClarification?.sourcePrompt?.trim()
8795
+ || this.recoverComputedColumnClarificationSourcePromptFromMessages(request);
8796
+ if (!sourcePrompt)
8797
+ return null;
8798
+ const normalizedSourcePrompt = this.normalizeLabel(sourcePrompt);
8799
+ if (!this.promptRequestsComputedColumnCreation(normalizedSourcePrompt))
8800
+ return null;
8801
+ const selectedField = this.selectedNewComputedFieldFromClarification(request);
8802
+ if (!selectedField)
8803
+ return null;
8804
+ const columns = this.currentTableColumnsForAuthoring();
8805
+ if (columns.some((column) => this.stringValue(column['field']).toLowerCase() === selectedField.toLowerCase())) {
8806
+ return null;
8807
+ }
8808
+ const baseFields = this.computedColumnBaseFieldsFromPrompt(normalizedSourcePrompt, columns);
8809
+ if (baseFields.length < 2)
8810
+ return null;
8811
+ const operation = {
8812
+ operationId: 'column.computed.add',
8813
+ target: { kind: 'column', field: selectedField },
8814
+ input: {
8815
+ field: selectedField,
8816
+ header: this.humanizeField(selectedField),
8817
+ expression: {
8818
+ cat: baseFields.flatMap((field, index) => (index === 0 ? [{ var: field }] : [' - ', { var: field }])),
8819
+ },
8820
+ outputType: 'string',
8821
+ dependencies: baseFields,
8822
+ },
8823
+ };
8824
+ return {
8825
+ type: 'patch',
8826
+ sessionId: request.sessionId,
8827
+ componentEditPlan: {
8828
+ kind: 'praxis.table.component-edit-plan',
8829
+ version: '1.0',
8830
+ componentId: this.adapter.componentId || request.componentId || 'praxis-table',
8831
+ ...operation,
8832
+ },
8833
+ explanation: `Vou criar a coluna calculada **${this.humanizeField(selectedField)}** combinando ${baseFields.map((field) => `**${this.humanizeField(field)}**`).join(' e ')}.`,
8834
+ warnings: [
8835
+ 'computed-column-clarification-continuation-materialized',
8836
+ 'A resposta curta de clarificacao foi materializada usando pendingClarification.sourcePrompt e colunas declaradas da tabela; a intencao primaria ja estava ancorada em coluna calculada.',
8837
+ ],
8838
+ };
8839
+ }
8840
+ selectedNewComputedFieldFromClarification(request) {
8841
+ const value = [
8842
+ request.action?.value,
8843
+ request.action?.displayPrompt,
8844
+ request.prompt,
8845
+ ]
8846
+ .map((candidate) => this.stringValue(candidate).trim())
8847
+ .find((candidate) => !!candidate);
8848
+ if (!value || /\s/u.test(value) || value.length > 80)
8849
+ return null;
8850
+ const cleaned = value.replace(/[^A-Za-z0-9_]/gu, '');
8851
+ return cleaned || null;
8852
+ }
8853
+ recoverComputedColumnClarificationSourcePromptFromMessages(request) {
8854
+ const messages = [...(request.messages ?? [])];
8855
+ if (messages.length < 2)
8856
+ return null;
8857
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8858
+ const message = messages[index];
8859
+ if (message.role !== 'assistant')
8860
+ continue;
8861
+ const assistantText = this.normalizeLabel(message.text ?? '');
8862
+ if (!assistantText.includes('coluna correta') && !assistantText.includes('qual coluna'))
8863
+ continue;
8864
+ const sourcePrompt = [...messages.slice(0, index)]
8865
+ .reverse()
8866
+ .find((candidate) => candidate.role === 'user')
8867
+ ?.text?.trim();
8868
+ if (sourcePrompt && this.promptRequestsComputedColumnCreation(this.normalizeLabel(sourcePrompt))) {
8869
+ return sourcePrompt;
8870
+ }
8871
+ }
8872
+ return null;
8873
+ }
8874
+ promptRequestsComputedColumnCreation(normalizedPrompt) {
8875
+ if (!normalizedPrompt)
8876
+ return false;
8877
+ const wantsComputed = normalizedPrompt.includes('calculad')
8878
+ || normalizedPrompt.includes('computed')
8879
+ || normalizedPrompt.includes('derivad')
8880
+ || normalizedPrompt.includes('formula');
8881
+ const wantsJoin = normalizedPrompt.includes('combin')
8882
+ || normalizedPrompt.includes('concat')
8883
+ || normalizedPrompt.includes('juntar')
8884
+ || normalizedPrompt.includes('unir')
8885
+ || normalizedPrompt.includes('mesclar')
8886
+ || normalizedPrompt.includes('merge');
8887
+ return wantsComputed && wantsJoin;
8888
+ }
8889
+ currentTableColumnsForAuthoring() {
8890
+ return this.toArray(this.adapter.getCurrentConfig()?.['columns'])
8891
+ .map((column) => this.toRecord(column))
8892
+ .filter((column) => !!column && !!this.stringValue(column['field']));
8893
+ }
8894
+ computedColumnBaseFieldsFromPrompt(normalizedPrompt, columns) {
8895
+ const fields = [];
8896
+ for (const column of columns) {
8897
+ const field = this.stringValue(column['field']);
8898
+ const header = this.stringValue(column['header']) || field;
8899
+ if (!field)
8900
+ continue;
8901
+ if (this.normalizedPromptMentionsColumn(normalizedPrompt, field)
8902
+ || this.normalizedPromptMentionsColumn(normalizedPrompt, header)) {
8903
+ fields.push(field);
8904
+ }
8905
+ }
8906
+ return [...new Set(fields)];
8907
+ }
8908
+ normalizedPromptMentionsColumn(normalizedPrompt, value) {
8909
+ const normalizedValue = this.normalizeLabel(value);
8910
+ if (!normalizedValue)
8911
+ return false;
8912
+ return normalizedPrompt.includes(normalizedValue);
8913
+ }
7765
8914
  describeBooleanStateRenderers(operations) {
7766
8915
  if (operations.length < 2)
7767
8916
  return null;
@@ -8482,7 +9631,6 @@ class TableAgenticAuthoringTurnFlow {
8482
9631
  };
8483
9632
  }
8484
9633
  buildComponentPresentationAuthoringSystemPolicy(contextHints, prompt) {
8485
- void contextHints;
8486
9634
  const normalizedPrompt = this.normalizeLabel(prompt);
8487
9635
  const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
8488
9636
  || this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
@@ -8501,6 +9649,11 @@ class TableAgenticAuthoringTurnFlow {
8501
9649
  const type = this.stringValue(column['type'] ?? column['dataType']) || 'unknown';
8502
9650
  return `- ${field} (${label}; ${type}; ${visible})`;
8503
9651
  });
9652
+ const targetGroundingLines = this.componentPresentationTargetGroundingLines(normalizedPrompt, columns);
9653
+ const contextField = this.stringValue(contextHints?.['targetField']);
9654
+ const contextTargetLine = contextField
9655
+ ? `Declared context target: ${contextField}.`
9656
+ : 'Declared context target: none.';
8504
9657
  return {
8505
9658
  role: 'system',
8506
9659
  content: [
@@ -8511,11 +9664,90 @@ class TableAgenticAuthoringTurnFlow {
8511
9664
  '- For compound prompts, preserve the action attached to each declared column: hide only columns in the hide/privacy scope, pin/fix columns with column.sticky.set, and highlight/destacar columns with renderer or style operations. Never hide a column that the user asked to pin or highlight.',
8512
9665
  '- If the user answers a previous component-edit clarification with a short option such as "sim", "opcao 2", or "prosseguir", continue the previous component-edit intent instead of treating the answer as a new filter request.',
8513
9666
  '- Ask clarification only when the declared source columns or authoring capabilities are insufficient to choose a safe componentEditPlan. Do not ask clarification solely to pick an internal field id for a newly named computed column.',
9667
+ '- Target grounding candidates below are ranking evidence after this turn has been resolved as presentation authoring; they are not the primary intent router.',
9668
+ '- When one high-confidence target candidate is declared, prefer that field for componentEditPlan targets unless the user explicitly names another field. Do not substitute a semantically adjacent field such as priority for status.',
9669
+ '- If the generated plan would target a different field than the high-confidence candidate, ask a short clarification instead of returning an executable componentEditPlan.',
8514
9670
  'Declared table columns:',
8515
9671
  ...columnLines,
9672
+ contextTargetLine,
9673
+ targetGroundingLines.length ? 'Target grounding candidates:' : 'Target grounding candidates: none declared.',
9674
+ ...targetGroundingLines,
8516
9675
  ].join('\n'),
8517
9676
  };
8518
9677
  }
9678
+ componentPresentationTargetGroundingLines(normalizedPrompt, columns) {
9679
+ return this.componentPresentationTargetGroundingCandidates(normalizedPrompt, columns)
9680
+ .map((candidate) => [
9681
+ `- ${candidate.field} (${candidate.label})`,
9682
+ `confidence=${candidate.confidence}`,
9683
+ `evidence=${candidate.evidence.join(', ')}`,
9684
+ ].join('; '));
9685
+ }
9686
+ componentPresentationTargetGroundingHints(prompt) {
9687
+ const normalizedPrompt = this.normalizeLabel(prompt);
9688
+ const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
9689
+ || this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
9690
+ if (!presentationIntent)
9691
+ return null;
9692
+ const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
9693
+ .map((column) => this.toRecord(column))
9694
+ .filter((column) => !!column && !!this.stringValue(column['field']))
9695
+ .slice(0, 20);
9696
+ const candidates = this.componentPresentationTargetGroundingCandidates(normalizedPrompt, columns);
9697
+ if (!candidates.length)
9698
+ return null;
9699
+ return {
9700
+ presentationTargetGrounding: {
9701
+ kind: 'praxis.table.presentation-target-grounding.v1',
9702
+ scope: 'presentation-authoring',
9703
+ candidates: candidates.map((candidate) => ({
9704
+ field: candidate.field,
9705
+ label: candidate.label,
9706
+ confidence: candidate.confidence,
9707
+ evidence: candidate.evidence,
9708
+ })),
9709
+ },
9710
+ };
9711
+ }
9712
+ componentPresentationTargetGroundingCandidates(normalizedPrompt, columns) {
9713
+ return columns
9714
+ .map((column) => {
9715
+ const field = this.stringValue(column['field']);
9716
+ if (!field)
9717
+ return null;
9718
+ const label = this.stringValue(column['header']) || this.humanizeField(field);
9719
+ const fieldScore = this.visualPresentationTargetFieldScore(normalizedPrompt, field);
9720
+ const labelScore = this.visualPresentationTargetFieldScore(normalizedPrompt, label);
9721
+ const valueMatches = this.componentPresentationObservedValueMatches(normalizedPrompt, field);
9722
+ const score = Math.max(fieldScore, labelScore) + (valueMatches.length ? 12 : 0);
9723
+ if (score <= 0)
9724
+ return null;
9725
+ const evidence = [
9726
+ fieldScore > 0 ? 'field mention' : null,
9727
+ labelScore > 0 ? 'header mention' : null,
9728
+ ...valueMatches.map((value) => `observed value "${value}"`),
9729
+ ].filter((value) => !!value);
9730
+ const confidence = score >= 12 ? 'high' : 'medium';
9731
+ return {
9732
+ field,
9733
+ label,
9734
+ score,
9735
+ confidence,
9736
+ evidence,
9737
+ };
9738
+ })
9739
+ .filter((candidate) => !!candidate)
9740
+ .sort((a, b) => b.score - a.score)
9741
+ .slice(0, 5);
9742
+ }
9743
+ componentPresentationObservedValueMatches(normalizedPrompt, field) {
9744
+ return this.adapterFieldValueSamples(field)
9745
+ .map((value) => this.stringValue(value))
9746
+ .filter((value) => !!value)
9747
+ .filter((value) => this.normalizedTextContainsApproxPhrase(normalizedPrompt, value))
9748
+ .map((value) => (value.length > 48 ? `${value.slice(0, 45)}...` : value))
9749
+ .slice(0, 3);
9750
+ }
8519
9751
  buildComponentLayoutAuthoringSystemPolicy(contextHints, prompt) {
8520
9752
  const normalizedPrompt = this.normalizeLabel(prompt);
8521
9753
  const position = this.relativeOrderPosition(normalizedPrompt);
@@ -8765,6 +9997,8 @@ class TableAgenticAuthoringTurnFlow {
8765
9997
  const record = this.toRecord(payload);
8766
9998
  if (!record)
8767
9999
  return false;
10000
+ if (this.isGovernedCategoricalSemanticsChoicePayload(record))
10001
+ return false;
8768
10002
  return this.isVisualPresentationClarificationText([
8769
10003
  record['label'],
8770
10004
  record['value'],
@@ -8793,6 +10027,48 @@ class TableAgenticAuthoringTurnFlow {
8793
10027
  'apresentação',
8794
10028
  ].some((token) => this.normalizedTextContainsApproxPhrase(text, token));
8795
10029
  }
10030
+ isGovernedCategoricalSemanticsChoicePayload(payload) {
10031
+ const record = this.toRecord(payload);
10032
+ if (!record)
10033
+ return false;
10034
+ const contextHintsText = this.safeJsonText(record['contextHints']);
10035
+ return this.isGovernedCategoricalSemanticsChoiceText([
10036
+ record['label'],
10037
+ record['value'],
10038
+ record['description'],
10039
+ contextHintsText,
10040
+ ].filter(Boolean).join(' '));
10041
+ }
10042
+ isGovernedCategoricalSemanticsChoiceText(value) {
10043
+ const text = this.normalizeLabel(value);
10044
+ if (!text)
10045
+ return false;
10046
+ return [
10047
+ 'author categorical field semantics',
10048
+ 'author categorical semantics',
10049
+ 'categorical field semantics',
10050
+ 'semantica visual governada',
10051
+ 'semântica visual governada',
10052
+ 'governed visual semantics',
10053
+ 'apply neutral categorical renderer',
10054
+ 'apply neutral categorical chip',
10055
+ 'aplicar chips neutros',
10056
+ 'chips neutros por enquanto',
10057
+ 'ungoverned neutral fallback',
10058
+ ]
10059
+ .map((token) => this.normalizeLabel(token))
10060
+ .some((token) => text.includes(token));
10061
+ }
10062
+ safeJsonText(value) {
10063
+ if (value === null || value === undefined)
10064
+ return '';
10065
+ try {
10066
+ return JSON.stringify(value) ?? '';
10067
+ }
10068
+ catch {
10069
+ return '';
10070
+ }
10071
+ }
8796
10072
  textMentionsFilterCatalogEntry(normalizedText) {
8797
10073
  if (!normalizedText || !this.filterFieldCatalogEntries.length)
8798
10074
  return false;
@@ -8896,17 +10172,40 @@ class TableAgenticAuthoringTurnFlow {
8896
10172
  const rawLabel = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
8897
10173
  const label = this.humanizeClarificationOptionLabel(rawLabel);
8898
10174
  const canonicalValue = option.value?.trim() || option.example?.trim() || label;
10175
+ const isGovernedCategoricalSemanticsChoice = this.isGovernedCategoricalSemanticsChoicePayload(option);
10176
+ const isVisualPresentation = !isGovernedCategoricalSemanticsChoice
10177
+ && this.isVisualPresentationClarificationPayload(option);
10178
+ const visualRenderer = isVisualPresentation
10179
+ ? this.visualPresentationRendererValue([rawLabel, canonicalValue].join(' '))
10180
+ : null;
10181
+ const optionHints = this.optionContextHints(option);
10182
+ const optionSelected = this.toRecord(optionHints?.['optionSelected']);
10183
+ const selection = this.toRecord(optionSelected?.['selection']);
10184
+ const visualField = visualRenderer
10185
+ ? this.stringValue(selection?.['field'])
10186
+ || this.stringValue(optionSelected?.['targetField'])
10187
+ || this.visualPresentationTargetField(response, request)
10188
+ : null;
10189
+ const prompt = visualRenderer
10190
+ ? this.visualPresentationClarificationPromptFromSelection(visualRenderer, visualField)
10191
+ : isGovernedCategoricalSemanticsChoice
10192
+ ? label
10193
+ : isVisualPresentation
10194
+ ? this.visualPresentationClarificationPrompt(option, canonicalValue)
10195
+ : label;
8899
10196
  return {
8900
10197
  id: `option-${index + 1}`,
8901
10198
  label,
8902
- prompt: label,
8903
- value: canonicalValue,
10199
+ prompt,
10200
+ value: visualRenderer ?? canonicalValue,
8904
10201
  kind: 'clarification-option',
8905
10202
  description: this.optionDescription(option),
8906
10203
  icon: this.optionIcon(option),
8907
10204
  tone: this.optionTone(option),
8908
10205
  presentation: this.optionPresentation(option) ?? this.defaultGuidedOptionPresentation(option),
8909
- contextHints: this.optionContextHints(option),
10206
+ contextHints: visualRenderer
10207
+ ? this.visualPresentationOptionContextHints(visualRenderer, visualField)
10208
+ : optionHints,
8910
10209
  };
8911
10210
  });
8912
10211
  }
@@ -8915,17 +10214,139 @@ class TableAgenticAuthoringTurnFlow {
8915
10214
  }
8916
10215
  return this.enhanceColumnClarificationOptions(response.options ?? [], response, request)
8917
10216
  .filter((option) => !!option?.trim())
8918
- .map((option, index) => ({
8919
- id: `option-${index + 1}`,
8920
- label: this.humanizeClarificationOptionLabel(option.trim()),
8921
- prompt: option.trim(),
8922
- kind: 'clarification-option',
10217
+ .map((option, index) => {
10218
+ const rawOption = option.trim();
10219
+ const label = this.humanizeClarificationOptionLabel(rawOption);
10220
+ const visualRenderer = this.visualPresentationRendererValue(rawOption);
10221
+ const visualField = visualRenderer
10222
+ ? this.visualPresentationTargetField(response, request)
10223
+ : null;
10224
+ const prompt = visualRenderer
10225
+ ? this.visualPresentationClarificationPromptFromSelection(visualRenderer, visualField)
10226
+ : rawOption;
10227
+ return {
10228
+ id: `option-${index + 1}`,
10229
+ label,
10230
+ prompt,
10231
+ value: visualRenderer ?? rawOption,
10232
+ kind: 'clarification-option',
10233
+ presentation: {
10234
+ kind: 'guided-option',
10235
+ icon: visualRenderer ? this.visualPresentationIcon(visualRenderer) : 'check',
10236
+ ctaLabel: visualRenderer ? 'Aplicar opção' : 'Usar esta opção',
10237
+ },
10238
+ ...(visualRenderer
10239
+ ? { contextHints: this.visualPresentationOptionContextHints(visualRenderer, visualField) }
10240
+ : {}),
10241
+ };
10242
+ });
10243
+ }
10244
+ visualPresentationClarificationPrompt(option, canonicalValue) {
10245
+ const contextHints = this.toRecord(option.contextHints);
10246
+ const optionSelected = this.toRecord(contextHints?.['optionSelected']);
10247
+ const selection = this.toRecord(optionSelected?.['selection']);
10248
+ const field = this.stringValue(selection?.['field']) || this.stringValue(optionSelected?.['targetField']);
10249
+ const rendererValue = this.stringValue(selection?.['value']) || canonicalValue;
10250
+ return this.visualPresentationClarificationPromptFromSelection(rendererValue, field);
10251
+ }
10252
+ visualPresentationClarificationPromptFromSelection(rendererValue, field) {
10253
+ return field
10254
+ ? `Aplique a apresentacao visual ${rendererValue} na coluna ${field}.`
10255
+ : `Aplique a apresentacao visual ${rendererValue}.`;
10256
+ }
10257
+ visualPresentationRendererValue(option) {
10258
+ if (!this.isVisualPresentationClarificationText(option))
10259
+ return null;
10260
+ const normalized = this.normalizeLabel(option);
10261
+ if (normalized.includes('two lines')
10262
+ || normalized.includes('two line')
10263
+ || normalized.includes('two-line')
10264
+ || normalized.includes('twolines')
10265
+ || normalized.includes('twoline')
10266
+ || normalized.includes('duas linhas')
10267
+ || normalized.includes('duaslinhas')
10268
+ || normalized.includes('duas_linhas')
10269
+ || normalized.includes('segunda linha')) {
10270
+ return 'two_lines';
10271
+ }
10272
+ if (normalized.includes('badge') || normalized.includes('chip') || normalized.includes('etiqueta')) {
10273
+ return 'badge';
10274
+ }
10275
+ if (normalized.includes('icone') || normalized.includes('icon')) {
10276
+ return 'icon';
10277
+ }
10278
+ if (normalized.includes('alinhamento') || normalized.includes('alinhar')) {
10279
+ return 'alignment';
10280
+ }
10281
+ return null;
10282
+ }
10283
+ visualPresentationTargetField(response, request) {
10284
+ const text = this.normalizeLabel([
10285
+ request?.prompt,
10286
+ request?.pendingClarification?.sourcePrompt,
10287
+ request?.pendingClarification?.assistantMessage,
10288
+ response.message,
10289
+ ...(response.questions ?? []),
10290
+ ].filter(Boolean).join(' '));
10291
+ const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
10292
+ .map((column) => this.toRecord(column))
10293
+ .filter((column) => !!column);
10294
+ const matched = columns
10295
+ .map((column) => {
10296
+ const field = this.stringValue(column['field']);
10297
+ if (!field)
10298
+ return null;
10299
+ const header = this.stringValue(column['header']) || field;
10300
+ const score = Math.max(this.visualPresentationTargetFieldScore(text, field), this.visualPresentationTargetFieldScore(text, header));
10301
+ return score > 0 ? { field, score } : null;
10302
+ })
10303
+ .filter((entry) => !!entry)
10304
+ .sort((a, b) => b.score - a.score)[0];
10305
+ if (matched)
10306
+ return matched.field;
10307
+ const visibleColumns = columns.filter((column) => column['visible'] !== false);
10308
+ const lastVisibleField = this.stringValue(visibleColumns[visibleColumns.length - 1]?.['field']);
10309
+ return lastVisibleField || null;
10310
+ }
10311
+ visualPresentationTargetFieldScore(normalizedText, value) {
10312
+ return this.filterFieldMentionVariants(value)
10313
+ .filter((variant) => !!variant && this.normalizedTextContainsApproxPhrase(normalizedText, variant))
10314
+ .reduce((score, variant) => Math.max(score, variant.length), 0);
10315
+ }
10316
+ visualPresentationOptionContextHints(rendererValue, field) {
10317
+ const selected = {
10318
+ selection: {
10319
+ mode: 'renderer',
10320
+ ...(field ? { field } : {}),
10321
+ value: rendererValue,
10322
+ },
10323
+ };
10324
+ if (field) {
10325
+ selected['targetField'] = field;
10326
+ }
10327
+ return {
10328
+ optionSelected: selected,
8923
10329
  presentation: {
8924
10330
  kind: 'guided-option',
8925
- icon: 'check',
8926
- ctaLabel: 'Usar esta opção',
10331
+ ctaLabel: 'Aplicar opção',
10332
+ icon: this.visualPresentationIcon(rendererValue),
10333
+ tone: 'primary',
8927
10334
  },
8928
- }));
10335
+ };
10336
+ }
10337
+ visualPresentationIcon(rendererValue) {
10338
+ switch (rendererValue) {
10339
+ case 'badge':
10340
+ return 'sell';
10341
+ case 'icon':
10342
+ return 'stars';
10343
+ case 'alignment':
10344
+ return 'format_align_left';
10345
+ case 'two_lines':
10346
+ return 'format_line_spacing';
10347
+ default:
10348
+ return 'check';
10349
+ }
8929
10350
  }
8930
10351
  filterFieldCatalogEntryForClarificationLabel(label) {
8931
10352
  const normalized = this.normalizeLabel(label);