@praxisui/table 8.0.0-beta.82 → 8.0.0-beta.85

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.
@@ -266,8 +266,7 @@ class TableAgenticAuthoringTurnFlow {
266
266
  || this.shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints));
267
267
  }
268
268
  shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints) {
269
- return this.selectedRecordsCountForTurn > 0
270
- && this.selectedRecordSurfaces(contextHints).length > 0;
269
+ return this.selectedRecordSurfaces(contextHints).length > 0;
271
270
  }
272
271
  selectedRecordSurfaces(contextHints) {
273
272
  const authoringContract = this.toRecord(contextHints?.['authoringContract']);
@@ -561,13 +560,14 @@ class TableAgenticAuthoringTurnFlow {
561
560
  ? this.adapter.compileAiResponse?.(response)
562
561
  : null;
563
562
  if (compiledExecutable?.patch && Object.keys(compiledExecutable.patch).length > 0) {
563
+ const normalizedExecutable = this.normalizeBulkRouteSelectionPayload(compiledExecutable, request);
564
564
  const warnings = [
565
565
  ...(response.warnings ?? []),
566
- ...(compiledExecutable.warnings ?? []),
566
+ ...(normalizedExecutable.warnings ?? []),
567
567
  ];
568
568
  const executableResponse = {
569
569
  ...response,
570
- patch: compiledExecutable.patch,
570
+ patch: normalizedExecutable.patch,
571
571
  warnings: warnings.length ? warnings : undefined,
572
572
  };
573
573
  delete executableResponse.type;
@@ -578,6 +578,10 @@ class TableAgenticAuthoringTurnFlow {
578
578
  if (continuedInvalidExecutable) {
579
579
  return continuedInvalidExecutable;
580
580
  }
581
+ const continuedBulkRouteAction = this.bulkRouteActionPlanForInvalidExecutable(response, request, compiledExecutable.warnings);
582
+ if (continuedBulkRouteAction) {
583
+ return this.compileAdapterResponse(continuedBulkRouteAction, request);
584
+ }
581
585
  return {
582
586
  type: 'error',
583
587
  message: compiledExecutable.message || 'O componentEditPlan da tabela nao passou na validacao de capacidades.',
@@ -592,12 +596,24 @@ class TableAgenticAuthoringTurnFlow {
592
596
  if (continuedSurfaceRuntimeOperation) {
593
597
  return continuedSurfaceRuntimeOperation;
594
598
  }
599
+ const continuedBulkRouteNarrative = this.bulkRouteActionPlanForNonExecutableNarrative(response, request);
600
+ if (continuedBulkRouteNarrative) {
601
+ return this.compileAdapterResponse(continuedBulkRouteNarrative, request);
602
+ }
603
+ const continuedBulkRouteAction = this.bulkRouteActionPlanForClarification(response, request);
604
+ if (continuedBulkRouteAction) {
605
+ return this.compileAdapterResponse(continuedBulkRouteAction, request);
606
+ }
595
607
  const continuedSurfaceRowAction = this.selectedRecordSurfaceRowActionPlanForClarification(response, request);
596
608
  if (continuedSurfaceRowAction) {
597
609
  return this.compileAdapterResponse(continuedSurfaceRowAction, request);
598
610
  }
599
611
  return response;
600
612
  }
613
+ const rowActionGuidance = this.recordSurfaceRowActionGuidanceForConsult(response, request);
614
+ if (rowActionGuidance) {
615
+ return rowActionGuidance;
616
+ }
601
617
  const compiled = this.adapter.compileAiResponse?.(response);
602
618
  if (!compiled && response.patch && Object.keys(response.patch).length > 0) {
603
619
  return {
@@ -610,9 +626,17 @@ class TableAgenticAuthoringTurnFlow {
610
626
  };
611
627
  }
612
628
  if (!compiled) {
629
+ const continuedBulkRouteNarrative = this.bulkRouteActionPlanForNonExecutableNarrative(response, request);
630
+ if (continuedBulkRouteNarrative) {
631
+ return this.compileAdapterResponse(continuedBulkRouteNarrative, request);
632
+ }
613
633
  return response;
614
634
  }
615
635
  if (compiled.type === 'error') {
636
+ const continuedBulkRouteAction = this.bulkRouteActionPlanForInvalidExecutable(response, request, compiled.warnings);
637
+ if (continuedBulkRouteAction) {
638
+ return this.compileAdapterResponse(continuedBulkRouteAction, request);
639
+ }
616
640
  const continuedInvalidExecutable = this.selectedRecordSurfaceRuntimeOperationForInvalidExecutable(response, request, compiled.warnings);
617
641
  if (continuedInvalidExecutable) {
618
642
  return continuedInvalidExecutable.componentEditPlan
@@ -645,17 +669,253 @@ class TableAgenticAuthoringTurnFlow {
645
669
  if (implicitSelectedFilterClarification) {
646
670
  return implicitSelectedFilterClarification;
647
671
  }
672
+ const normalizedCompiled = this.normalizeBulkRouteSelectionPayload(compiled, request);
648
673
  const warnings = [
649
674
  ...(response.warnings ?? []),
650
- ...(compiled.warnings ?? []),
675
+ ...(normalizedCompiled.warnings ?? []),
651
676
  ];
652
677
  return {
653
678
  ...response,
654
- ...compiled,
655
- patch: compiled.patch,
679
+ ...normalizedCompiled,
680
+ patch: normalizedCompiled.patch,
656
681
  warnings: warnings.length ? warnings : undefined,
657
682
  };
658
683
  }
684
+ normalizeBulkRouteSelectionPayload(compiled, request) {
685
+ if (!compiled?.patch || !request)
686
+ return compiled;
687
+ const text = this.normalizeLabel([
688
+ ...(request.messages ?? [])
689
+ .filter((message) => message?.role === 'user')
690
+ .slice(-2)
691
+ .map((message) => message?.text ?? ''),
692
+ request.prompt ?? '',
693
+ ].join(' '));
694
+ if (!this.textMentionsBulkActionRequest(text))
695
+ return compiled;
696
+ const routePath = this.extractRoutePath(text);
697
+ const patch = this.cloneJson(compiled.patch);
698
+ const componentEditPlan = compiled.componentEditPlan
699
+ ? this.cloneJson(compiled.componentEditPlan)
700
+ : undefined;
701
+ const bulkActions = patch?.actions?.bulk?.actions;
702
+ let changed = false;
703
+ if (Array.isArray(bulkActions)) {
704
+ for (const action of bulkActions) {
705
+ if (!action || typeof action !== 'object')
706
+ continue;
707
+ if (this.ensureBulkRouteActionSelectionQuery(action, routePath)) {
708
+ changed = true;
709
+ }
710
+ }
711
+ }
712
+ if (componentEditPlan && this.ensureBulkRouteActionSelectionQueryOnPlan(componentEditPlan, routePath)) {
713
+ changed = true;
714
+ }
715
+ if (!Array.isArray(bulkActions) && !componentEditPlan)
716
+ return compiled;
717
+ if (!changed)
718
+ return compiled;
719
+ return {
720
+ ...compiled,
721
+ ...(componentEditPlan ? { componentEditPlan } : {}),
722
+ patch,
723
+ warnings: [
724
+ ...(compiled.warnings ?? []),
725
+ 'bulk-route-action-selection-query-normalized',
726
+ ],
727
+ };
728
+ }
729
+ ensureBulkRouteActionSelectionQueryOnPlan(plan, routePath) {
730
+ if (!plan || typeof plan !== 'object')
731
+ return false;
732
+ let changed = false;
733
+ for (const key of ['input', 'value']) {
734
+ if (this.ensureBulkRouteActionSelectionQuery(plan[key], routePath)) {
735
+ changed = true;
736
+ }
737
+ }
738
+ if (Array.isArray(plan.operations)) {
739
+ for (const operation of plan.operations) {
740
+ if (!operation || typeof operation !== 'object')
741
+ continue;
742
+ for (const key of ['input', 'value']) {
743
+ if (this.ensureBulkRouteActionSelectionQuery(operation[key], routePath)) {
744
+ changed = true;
745
+ }
746
+ }
747
+ }
748
+ }
749
+ return changed;
750
+ }
751
+ ensureBulkRouteActionSelectionQuery(action, routePath) {
752
+ if (!action || typeof action !== 'object')
753
+ return false;
754
+ let changed = false;
755
+ if (this.ensureBulkRouteActionGlobalRouteRef(action, routePath)) {
756
+ changed = true;
757
+ }
758
+ if (this.ensureGlobalRouteActionSelectionQuery(action.globalAction)) {
759
+ changed = true;
760
+ }
761
+ if (Array.isArray(action.effects)) {
762
+ for (const effect of action.effects) {
763
+ if (effect?.kind === 'global-action' && this.ensureGlobalRouteActionSelectionQuery(effect.globalAction)) {
764
+ changed = true;
765
+ }
766
+ }
767
+ }
768
+ return changed;
769
+ }
770
+ ensureBulkRouteActionGlobalRouteRef(action, routePath) {
771
+ if (!routePath)
772
+ return false;
773
+ if (this.hasGlobalRouteActionRef(action))
774
+ return false;
775
+ const configuredAction = this.stringValue(action.action);
776
+ const looksLikeRouteAction = configuredAction === 'navigation.openRoute'
777
+ || configuredAction === 'navigation.open route'
778
+ || this.stringValue(action.globalAction?.actionId) === 'navigation.openRoute';
779
+ const hasRoutePayload = this.stringValue(action.payload?.path)
780
+ || this.stringValue(action.globalAction?.payload?.path)
781
+ || routePath;
782
+ if (!looksLikeRouteAction && !hasRoutePayload)
783
+ return false;
784
+ const globalAction = {
785
+ actionId: 'navigation.openRoute',
786
+ payload: {
787
+ path: this.stringValue(action.payload?.path)
788
+ || this.stringValue(action.globalAction?.payload?.path)
789
+ || routePath,
790
+ query: { ids: '${runtime.selectedIds}' },
791
+ },
792
+ };
793
+ action.globalAction = globalAction;
794
+ action.effects = [{ kind: 'global-action', globalAction }];
795
+ if (!this.stringValue(action.action)) {
796
+ action.action = 'navigation.openRoute';
797
+ }
798
+ return true;
799
+ }
800
+ hasGlobalRouteActionRef(action) {
801
+ if (this.stringValue(action?.globalAction?.actionId) === 'navigation.openRoute') {
802
+ return true;
803
+ }
804
+ if (!Array.isArray(action?.effects))
805
+ return false;
806
+ return action.effects.some((effect) => effect?.kind === 'global-action'
807
+ && this.stringValue(effect.globalAction?.actionId) === 'navigation.openRoute');
808
+ }
809
+ ensureGlobalRouteActionSelectionQuery(globalAction) {
810
+ if (!globalAction || typeof globalAction !== 'object')
811
+ return false;
812
+ if (globalAction.actionId !== 'navigation.openRoute')
813
+ return false;
814
+ const payload = globalAction.payload;
815
+ if (!payload || typeof payload !== 'object')
816
+ return false;
817
+ if (!this.stringValue(payload.path))
818
+ return false;
819
+ if (!payload.query || typeof payload.query !== 'object' || Array.isArray(payload.query)) {
820
+ payload.query = {};
821
+ }
822
+ if (payload.query.ids !== undefined && payload.query.ids !== null && payload.query.ids !== '') {
823
+ return false;
824
+ }
825
+ payload.query.ids = '${runtime.selectedIds}';
826
+ return true;
827
+ }
828
+ recordSurfaceRowActionGuidanceForConsult(response, request) {
829
+ if (!request || !this.promptAsksRowActionCapabilities(request))
830
+ return null;
831
+ if (!this.responseCarriesRowActionAddPlan(response))
832
+ return null;
833
+ const surfaces = this.selectedRecordSurfaces(this.contextHintsFor(request));
834
+ if (!surfaces.length)
835
+ return null;
836
+ const labels = [...new Set(surfaces
837
+ .map((surface) => {
838
+ const resourceSurface = this.toRecord(surface['resourceSurface']);
839
+ return this.stringValue(surface['label'])
840
+ || this.stringValue(resourceSurface?.['title'])
841
+ || this.stringValue(surface['id']);
842
+ })
843
+ .filter((label) => !!label))];
844
+ if (!labels.length)
845
+ return null;
846
+ return {
847
+ type: 'info',
848
+ message: [
849
+ 'Você pode adicionar botões de linha para superfícies relacionadas declaradas para este recurso.',
850
+ `Opções disponíveis agora: ${labels.map((label) => `**${label}**`).join(', ')}.`,
851
+ 'Quando quiser materializar uma delas, peça algo como "adicione um botão na linha para abrir Histórico de folha".',
852
+ ].join(' '),
853
+ warnings: [
854
+ 'row-action-capability-question-preserved-as-consult',
855
+ 'Residual consult guard acted only after the LLM proposed rowAction.add for a row-action capability-discovery question while declared recordSurfaces were available.',
856
+ ],
857
+ };
858
+ }
859
+ promptAsksRowActionCapabilities(request) {
860
+ const currentPrompt = this.normalizeLabel(request.prompt ?? '');
861
+ const recentUserContext = this.normalizeLabel((request.messages ?? [])
862
+ .filter((message) => message?.role === 'user')
863
+ .slice(-2)
864
+ .map((message) => message?.text ?? '')
865
+ .join(' '));
866
+ const text = [recentUserContext, currentPrompt].filter(Boolean).join(' ');
867
+ if (!this.textMentionsRowActionRequest(text))
868
+ return false;
869
+ const asksAvailability = [
870
+ 'quais',
871
+ 'qual',
872
+ 'o que',
873
+ 'que botoes',
874
+ 'que botões',
875
+ 'botoes posso',
876
+ 'botões posso',
877
+ 'acoes disponiveis',
878
+ 'ações disponíveis',
879
+ 'acoes estao disponiveis',
880
+ 'ações estão disponíveis',
881
+ 'posso adicionar',
882
+ 'can i add',
883
+ 'available',
884
+ 'what actions',
885
+ 'which actions',
886
+ 'which buttons',
887
+ ].some((needle) => text.includes(this.normalizeLabel(needle)));
888
+ if (!asksAvailability)
889
+ return false;
890
+ const explicitMaterialization = [
891
+ 'adicione',
892
+ 'adicionar agora',
893
+ 'crie',
894
+ 'criar agora',
895
+ 'coloque',
896
+ 'aplique',
897
+ 'materialize',
898
+ 'create a',
899
+ 'add a',
900
+ ].some((needle) => currentPrompt.includes(this.normalizeLabel(needle)));
901
+ return !explicitMaterialization;
902
+ }
903
+ responseCarriesRowActionAddPlan(response) {
904
+ const record = response;
905
+ return [
906
+ this.toRecord(record['componentEditPlan']),
907
+ this.toRecord(this.toRecord(record['patch'])?.['componentEditPlan']),
908
+ this.toRecord(record['tableEditPlan']),
909
+ this.toRecord(record['editPlan']),
910
+ ].some((plan) => {
911
+ if (!plan)
912
+ return false;
913
+ if (this.stringValue(plan['operationId']) === 'rowAction.add')
914
+ return true;
915
+ const operations = Array.isArray(plan['operations']) ? plan['operations'] : [];
916
+ return operations.some((operation) => this.stringValue(this.toRecord(operation)?.['operationId']) === 'rowAction.add');
917
+ });
918
+ }
659
919
  responseCarriesClarificationChoices(response) {
660
920
  if (response.type !== 'clarification')
661
921
  return false;
@@ -936,7 +1196,7 @@ class TableAgenticAuthoringTurnFlow {
936
1196
  ...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
937
1197
  request.prompt ?? '',
938
1198
  ].join(' '));
939
- if (!this.textMentionsRowActionRequest(normalizedPrompt || normalizedConversation)) {
1199
+ if (!this.textMentionsRowActionRequest(`${normalizedPrompt} ${normalizedConversation}`)) {
940
1200
  return null;
941
1201
  }
942
1202
  const clarificationKind = this.selectedRecordSurfaceRowActionClarificationKind(response);
@@ -956,11 +1216,19 @@ class TableAgenticAuthoringTurnFlow {
956
1216
  if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
957
1217
  return null;
958
1218
  }
959
- const rowActionResponse = this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, 'selected-record-surface-row-action-continued-from-clarification', clarificationKind === 'technical'
960
- ? 'selected-record-surface-row-action-continued-from-technical-clarification'
961
- : 'selected-record-surface-row-action-continued-from-generic-clarification', clarificationKind === 'technical'
962
- ? 'Residual continuity guard acted only after LLM asked for technical row-action details while declared recordSurfaces and conversation history already grounded the target surface.'
963
- : 'Residual continuity guard acted only after LLM asked a generic dataset/listing clarification while declared recordSurfaces and a row-action request already grounded the target surface.');
1219
+ const hasSelectedRecords = this.selectedRecordsCountForTurn > 0;
1220
+ const source = hasSelectedRecords
1221
+ ? 'selected-record-surface-row-action-continued-from-clarification'
1222
+ : 'record-surface-row-action-continued-from-clarification';
1223
+ const warning = hasSelectedRecords
1224
+ ? (clarificationKind === 'technical'
1225
+ ? 'selected-record-surface-row-action-continued-from-technical-clarification'
1226
+ : 'selected-record-surface-row-action-continued-from-generic-clarification')
1227
+ : 'record-surface-row-action-continued-from-clarification';
1228
+ const warningDetail = clarificationKind === 'technical'
1229
+ ? `Residual continuity guard acted only after LLM asked for technical row-action details while declared recordSurfaces${hasSelectedRecords ? ' and conversation history' : ''} already grounded the target surface.`
1230
+ : 'Residual continuity guard acted only after LLM asked a generic dataset/listing clarification while declared recordSurfaces and a row-action request already grounded the target surface.';
1231
+ const rowActionResponse = this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, source, warning, warningDetail);
964
1232
  if (!rowActionResponse)
965
1233
  return null;
966
1234
  // Residual continuity guard: this runs only after the LLM returned a
@@ -978,7 +1246,7 @@ class TableAgenticAuthoringTurnFlow {
978
1246
  ...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
979
1247
  request.prompt ?? '',
980
1248
  ].join(' '));
981
- if (!this.textMentionsRowActionRequest(normalizedPrompt || normalizedConversation)) {
1249
+ if (!this.textMentionsRowActionRequest(`${normalizedPrompt} ${normalizedConversation}`)) {
982
1250
  return null;
983
1251
  }
984
1252
  const responseText = this.normalizeLabel([
@@ -1073,9 +1341,14 @@ class TableAgenticAuthoringTurnFlow {
1073
1341
  if (!request || !this.responseMayContainExecutableEnvelope(response))
1074
1342
  return null;
1075
1343
  const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
1076
- if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
1344
+ const normalizedConversation = this.normalizeLabel([
1345
+ ...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
1346
+ request.prompt ?? '',
1347
+ ].join(' '));
1348
+ const requestText = `${normalizedPrompt} ${normalizedConversation}`;
1349
+ if (!this.textMentionsRecordSurfaceOpenRequest(requestText))
1077
1350
  return null;
1078
- if (this.promptRequestsSimilarRecords(normalizedPrompt))
1351
+ if (this.promptRequestsSimilarRecords(requestText))
1079
1352
  return null;
1080
1353
  const contextHints = this.contextHintsFor(request);
1081
1354
  const surfaces = this.selectedRecordSurfaces(contextHints);
@@ -1091,14 +1364,14 @@ class TableAgenticAuthoringTurnFlow {
1091
1364
  const ranked = surfaces
1092
1365
  .map((surface) => ({
1093
1366
  surface,
1094
- score: this.selectedRecordSurfacePromptScore(`${normalizedPrompt} ${responseEvidence}`, surface),
1367
+ score: this.selectedRecordSurfacePromptScore(`${requestText} ${responseEvidence}`, surface),
1095
1368
  }))
1096
1369
  .filter((entry) => entry.score >= 2)
1097
1370
  .sort((left, right) => right.score - left.score);
1098
1371
  if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
1099
1372
  return null;
1100
1373
  }
1101
- if (this.textMentionsRowActionRequest(normalizedPrompt)) {
1374
+ if (this.textMentionsRowActionRequest(requestText)) {
1102
1375
  const rowActionResponse = this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, 'selected-record-surface-row-action-invalid-executable-continuity-guard', 'selected-record-surface-row-action-continued-from-invalid-executable', 'Residual continuity guard acted only after LLM emitted an executable envelope that failed table component capability validation for a row-action request while a declared recordSurface uniquely matched the selected-record request.');
1103
1376
  if (rowActionResponse)
1104
1377
  return rowActionResponse;
@@ -1173,6 +1446,116 @@ class TableAgenticAuthoringTurnFlow {
1173
1446
  warnings: [warning, warningDetail],
1174
1447
  };
1175
1448
  }
1449
+ bulkRouteActionPlanForClarification(response, request) {
1450
+ if (response.type !== 'clarification' || !request)
1451
+ return null;
1452
+ const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
1453
+ if (!this.textMentionsBulkActionRequest(normalizedPrompt))
1454
+ return null;
1455
+ if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
1456
+ return null;
1457
+ const routePath = this.extractRoutePath(request.prompt ?? '');
1458
+ if (!routePath)
1459
+ return null;
1460
+ const clarificationKind = this.globalActionClarificationKind(response);
1461
+ if (!clarificationKind)
1462
+ return null;
1463
+ return this.buildBulkRouteActionPlan(routePath, 'bulk-route-action-continued-from-clarification', [
1464
+ 'bulk-route-action-continued-from-technical-clarification',
1465
+ 'Residual continuity guard acted only after LLM asked for technical bulk route details while a route path and selected-record bulk action intent were already grounded.',
1466
+ ]);
1467
+ }
1468
+ bulkRouteActionPlanForInvalidExecutable(response, request, validationWarnings = []) {
1469
+ if (!request)
1470
+ return null;
1471
+ if (!response.componentEditPlan && !response.patch)
1472
+ return null;
1473
+ const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
1474
+ if (!this.textMentionsBulkActionRequest(normalizedPrompt))
1475
+ return null;
1476
+ if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
1477
+ return null;
1478
+ const routePath = this.extractRoutePath(request.prompt ?? '');
1479
+ if (!routePath)
1480
+ return null;
1481
+ return this.buildBulkRouteActionPlan(routePath, 'bulk-route-action-continued-from-invalid-executable', [
1482
+ ...validationWarnings,
1483
+ 'bulk-route-action-continued-from-invalid-executable',
1484
+ 'Residual continuity guard acted only after LLM proposed an invalid executable table plan while route path and selected-record bulk action intent were already grounded.',
1485
+ ]);
1486
+ }
1487
+ bulkRouteActionPlanForNonExecutableNarrative(response, request) {
1488
+ if (!request)
1489
+ return null;
1490
+ const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
1491
+ if (!this.textMentionsBulkActionRequest(normalizedPrompt))
1492
+ return null;
1493
+ if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
1494
+ return null;
1495
+ const routePath = this.extractRoutePath(request.prompt ?? '');
1496
+ if (!routePath)
1497
+ return null;
1498
+ const narrative = this.normalizeLabel([
1499
+ response.message ?? '',
1500
+ response.explanation ?? '',
1501
+ ...(response.warnings ?? []),
1502
+ ].join(' '));
1503
+ const hasCanonicalDecision = this.normalizedTextIncludesAny(narrative, ['componentEditPlan', 'component edit plan'])
1504
+ && this.normalizedTextIncludesAny(narrative, ['bulkAction.add', 'bulk action.add'])
1505
+ && this.normalizedTextIncludesAny(narrative, ['navigation.openRoute', 'navigation.open route'])
1506
+ && this.normalizedTextIncludesAny(narrative, ['selectedIds', 'selected ids', 'runtime.selectedIds']);
1507
+ if (!hasCanonicalDecision)
1508
+ return null;
1509
+ return this.buildBulkRouteActionPlan(routePath, 'bulk-route-action-continued-from-non-executable-narrative', [
1510
+ 'bulk-route-action-continued-from-non-executable-narrative',
1511
+ 'Residual continuity guard acted only after LLM described the canonical bulkAction.add/navigation.openRoute decision without returning an executable componentEditPlan envelope.',
1512
+ ]);
1513
+ }
1514
+ buildBulkRouteActionPlan(routePath, source, warnings) {
1515
+ const actionId = `open-${this.slugifyActionId(routePath)}-selected`;
1516
+ const label = this.humanizeBulkRouteActionLabel(routePath);
1517
+ const globalAction = {
1518
+ actionId: 'navigation.openRoute',
1519
+ payload: {
1520
+ path: routePath,
1521
+ query: { ids: '${runtime.selectedIds}' },
1522
+ },
1523
+ };
1524
+ const bulkAction = {
1525
+ id: actionId,
1526
+ label,
1527
+ action: 'navigation.openRoute',
1528
+ icon: 'route',
1529
+ globalAction,
1530
+ effects: [
1531
+ {
1532
+ kind: 'global-action',
1533
+ globalAction,
1534
+ },
1535
+ ],
1536
+ };
1537
+ // Residual continuity guard: this executes only after the LLM already
1538
+ // resolved the turn as a clarification, while the user-provided route path
1539
+ // and the declared bulkAction.add/globalAction contract are sufficient to
1540
+ // materialize the operation. It grounds payload details in canonical runtime
1541
+ // selection templates instead of using local text as primary intent routing.
1542
+ return {
1543
+ type: 'patch',
1544
+ componentEditPlan: {
1545
+ kind: 'praxis.table.component-edit-plan',
1546
+ version: '1.0',
1547
+ componentId: 'praxis-table',
1548
+ operationId: 'bulkAction.add',
1549
+ changeKind: 'add_bulk_action',
1550
+ capabilityPath: 'actions.bulk.actions[]',
1551
+ input: bulkAction,
1552
+ value: bulkAction,
1553
+ source,
1554
+ },
1555
+ explanation: `Vou adicionar a ação ${label} para os registros selecionados.`,
1556
+ warnings,
1557
+ };
1558
+ }
1176
1559
  selectedRecordSurfaceRowActionClarificationKind(response) {
1177
1560
  const text = this.normalizeLabel([
1178
1561
  response.message ?? '',
@@ -1186,6 +1569,30 @@ class TableAgenticAuthoringTurnFlow {
1186
1569
  'mais detalhes sobre surface',
1187
1570
  'mais detalhes sobre button',
1188
1571
  'mais detalhes sobre placement',
1572
+ 'mais detalhes sobre openmode',
1573
+ 'mais detalhes sobre open mode',
1574
+ 'mais detalhes sobre iconorstyle',
1575
+ 'mais detalhes sobre appliesto',
1576
+ 'mais detalhes sobre actionlabel',
1577
+ 'mais detalhes sobre openbehavior',
1578
+ 'mais detalhes sobre actionbehavior',
1579
+ 'mais detalhes sobre destinationsurface',
1580
+ 'mais detalhes sobre iconlabel',
1581
+ 'qual condicao deve ser usada',
1582
+ 'qual condição deve ser usada',
1583
+ 'mais detalhes sobre action behavior',
1584
+ 'o alvo do ajuste',
1585
+ 'nome que voce prefere',
1586
+ 'nome que você prefere',
1587
+ 'openmode',
1588
+ 'open mode',
1589
+ 'iconorstyle',
1590
+ 'appliesto',
1591
+ 'actionlabel',
1592
+ 'openbehavior',
1593
+ 'actionbehavior',
1594
+ 'destinationsurface',
1595
+ 'iconlabel',
1189
1596
  ].some((needle) => text.includes(needle))) {
1190
1597
  return 'technical';
1191
1598
  }
@@ -1202,16 +1609,68 @@ class TableAgenticAuthoringTurnFlow {
1202
1609
  }
1203
1610
  return null;
1204
1611
  }
1612
+ globalActionClarificationKind(response) {
1613
+ const text = this.normalizeLabel([
1614
+ response.message ?? '',
1615
+ response.explanation ?? '',
1616
+ ...(response.questions ?? []),
1617
+ ].join(' '));
1618
+ if ([
1619
+ 'recordidparamname',
1620
+ 'record id param name',
1621
+ 'o alvo do ajuste',
1622
+ 'qual formato aplicar',
1623
+ 'mais detalhes sobre target',
1624
+ 'mais detalhes sobre payload',
1625
+ 'mais detalhes sobre route',
1626
+ 'mais detalhes sobre openmode',
1627
+ 'mais detalhes sobre open mode',
1628
+ ].some((needle) => text.includes(this.normalizeLabel(needle)))) {
1629
+ return 'technical';
1630
+ }
1631
+ return null;
1632
+ }
1205
1633
  textMentionsRowActionRequest(normalizedText) {
1206
1634
  return [
1207
1635
  'botao',
1208
1636
  'acao de linha',
1209
1637
  'acoes de linha',
1638
+ 'acao por registro',
1639
+ 'acoes por registro',
1210
1640
  'nas linhas',
1211
1641
  'por linha',
1212
1642
  'em cada linha',
1213
1643
  ].some((needle) => normalizedText.includes(needle));
1214
1644
  }
1645
+ textMentionsBulkActionRequest(normalizedText) {
1646
+ return [
1647
+ 'acao em lote',
1648
+ 'acoes em lote',
1649
+ 'registros selecionados',
1650
+ 'linhas selecionadas',
1651
+ 'selecionados',
1652
+ 'bulk action',
1653
+ ].some((needle) => normalizedText.includes(this.normalizeLabel(needle)));
1654
+ }
1655
+ normalizedTextIncludesAny(normalizedText, needles) {
1656
+ return needles.some((needle) => normalizedText.includes(this.normalizeLabel(needle)));
1657
+ }
1658
+ extractRoutePath(text) {
1659
+ const match = String(text || '').match(/(?:^|\s)(\/[A-Za-z0-9._~:/?#[\]@!$&'()*+,;=%-]+)(?=$|\s|[,.])/u);
1660
+ return match?.[1]?.replace(/[,.]$/u, '') ?? '';
1661
+ }
1662
+ humanizeBulkRouteActionLabel(path) {
1663
+ const segments = path
1664
+ .split(/[?#]/u)[0]
1665
+ .split('/')
1666
+ .map((segment) => segment.trim())
1667
+ .filter(Boolean);
1668
+ const lastSegments = segments.slice(-2);
1669
+ const label = lastSegments
1670
+ .map((segment) => this.humanizeField(segment))
1671
+ .join(' - ');
1672
+ return label ? `Abrir ${label}` : 'Abrir rota dos selecionados';
1673
+ }
1215
1674
  textMentionsRecordSurfaceOpenRequest(normalizedText) {
1216
1675
  return [
1217
1676
  'abrir',
@@ -2031,6 +2490,9 @@ class TableAgenticAuthoringTurnFlow {
2031
2490
  case 'rowAction.add':
2032
2491
  case 'add_row_action':
2033
2492
  return `Vou criar um botao em cada linha para abrir **${this.stringValue(input['label']) || this.stringValue(input['id']) || 'a opcao solicitada'}**.`;
2493
+ case 'bulkAction.add':
2494
+ case 'add_bulk_action':
2495
+ return `Vou adicionar a acao **${this.stringValue(input['label']) || this.stringValue(input['id']) || 'solicitada'}** para os registros selecionados.`;
2034
2496
  case 'export.configure':
2035
2497
  return this.describeExportConfigure(input);
2036
2498
  case 'appearance.density.set':
@@ -3056,6 +3518,14 @@ class TableAgenticAuthoringTurnFlow {
3056
3518
  return {};
3057
3519
  }
3058
3520
  }
3521
+ cloneJson(value) {
3522
+ try {
3523
+ return JSON.parse(JSON.stringify(value));
3524
+ }
3525
+ catch {
3526
+ return value;
3527
+ }
3528
+ }
3059
3529
  toRecord(value) {
3060
3530
  return value && typeof value === 'object' && !Array.isArray(value)
3061
3531
  ? value