@praxisui/table 8.0.0-beta.81 → 8.0.0-beta.84

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,22 +578,42 @@ 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.',
584
588
  warnings: compiledExecutable.warnings,
585
589
  };
586
590
  }
591
+ const continuedSurfaceRowActionFromInfo = this.selectedRecordSurfaceRowActionPlanForInfo(response, request);
592
+ if (continuedSurfaceRowActionFromInfo) {
593
+ return this.compileAdapterResponse(continuedSurfaceRowActionFromInfo, request);
594
+ }
587
595
  const continuedSurfaceRuntimeOperation = this.selectedRecordSurfaceRuntimeOperationForInfo(response, request);
588
596
  if (continuedSurfaceRuntimeOperation) {
589
597
  return continuedSurfaceRuntimeOperation;
590
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
+ }
591
607
  const continuedSurfaceRowAction = this.selectedRecordSurfaceRowActionPlanForClarification(response, request);
592
608
  if (continuedSurfaceRowAction) {
593
609
  return this.compileAdapterResponse(continuedSurfaceRowAction, request);
594
610
  }
595
611
  return response;
596
612
  }
613
+ const rowActionGuidance = this.recordSurfaceRowActionGuidanceForConsult(response, request);
614
+ if (rowActionGuidance) {
615
+ return rowActionGuidance;
616
+ }
597
617
  const compiled = this.adapter.compileAiResponse?.(response);
598
618
  if (!compiled && response.patch && Object.keys(response.patch).length > 0) {
599
619
  return {
@@ -606,12 +626,22 @@ class TableAgenticAuthoringTurnFlow {
606
626
  };
607
627
  }
608
628
  if (!compiled) {
629
+ const continuedBulkRouteNarrative = this.bulkRouteActionPlanForNonExecutableNarrative(response, request);
630
+ if (continuedBulkRouteNarrative) {
631
+ return this.compileAdapterResponse(continuedBulkRouteNarrative, request);
632
+ }
609
633
  return response;
610
634
  }
611
635
  if (compiled.type === 'error') {
636
+ const continuedBulkRouteAction = this.bulkRouteActionPlanForInvalidExecutable(response, request, compiled.warnings);
637
+ if (continuedBulkRouteAction) {
638
+ return this.compileAdapterResponse(continuedBulkRouteAction, request);
639
+ }
612
640
  const continuedInvalidExecutable = this.selectedRecordSurfaceRuntimeOperationForInvalidExecutable(response, request, compiled.warnings);
613
641
  if (continuedInvalidExecutable) {
614
- return continuedInvalidExecutable;
642
+ return continuedInvalidExecutable.componentEditPlan
643
+ ? this.compileAdapterResponse(continuedInvalidExecutable, request)
644
+ : continuedInvalidExecutable;
615
645
  }
616
646
  return {
617
647
  type: 'error',
@@ -639,17 +669,253 @@ class TableAgenticAuthoringTurnFlow {
639
669
  if (implicitSelectedFilterClarification) {
640
670
  return implicitSelectedFilterClarification;
641
671
  }
672
+ const normalizedCompiled = this.normalizeBulkRouteSelectionPayload(compiled, request);
642
673
  const warnings = [
643
674
  ...(response.warnings ?? []),
644
- ...(compiled.warnings ?? []),
675
+ ...(normalizedCompiled.warnings ?? []),
645
676
  ];
646
677
  return {
647
678
  ...response,
648
- ...compiled,
649
- patch: compiled.patch,
679
+ ...normalizedCompiled,
680
+ patch: normalizedCompiled.patch,
650
681
  warnings: warnings.length ? warnings : undefined,
651
682
  };
652
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
+ }
653
919
  responseCarriesClarificationChoices(response) {
654
920
  if (response.type !== 'clarification')
655
921
  return false;
@@ -930,7 +1196,7 @@ class TableAgenticAuthoringTurnFlow {
930
1196
  ...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
931
1197
  request.prompt ?? '',
932
1198
  ].join(' '));
933
- if (!this.textMentionsRowActionRequest(normalizedPrompt || normalizedConversation)) {
1199
+ if (!this.textMentionsRowActionRequest(`${normalizedPrompt} ${normalizedConversation}`)) {
934
1200
  return null;
935
1201
  }
936
1202
  const clarificationKind = this.selectedRecordSurfaceRowActionClarificationKind(response);
@@ -950,52 +1216,65 @@ class TableAgenticAuthoringTurnFlow {
950
1216
  if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
951
1217
  return null;
952
1218
  }
953
- const surface = ranked[0].surface;
954
- const resourceSurface = this.toRecord(surface['resourceSurface']);
955
- if (!this.isResourceSurfaceCatalogDigest(resourceSurface)) {
956
- return null;
957
- }
958
- const surfaceId = this.stringValue(surface['id']) || this.stringValue(resourceSurface['id']);
959
- if (!surfaceId)
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);
1232
+ if (!rowActionResponse)
960
1233
  return null;
961
- const surfaceLabel = this.stringValue(surface['label'])
962
- || this.stringValue(resourceSurface['title'])
963
- || this.humanizeField(surfaceId);
964
- const actionId = `open-${this.slugifyActionId(surfaceId)}`;
965
- const rowAction = {
966
- id: actionId,
967
- label: surfaceLabel,
968
- action: 'surface.open',
969
- icon: 'open_in_new',
970
- recordSurface: this.toAiJsonObject(resourceSurface),
971
- };
972
1234
  // Residual continuity guard: this runs only after the LLM returned a
973
1235
  // clarification/info response for a row-button request. The target surface
974
1236
  // is ranked from declared canonical recordSurfaces and recent conversation
975
1237
  // context, so the guard repairs materialization without becoming primary
976
1238
  // intent routing by local command text.
977
- return {
978
- type: 'patch',
979
- componentEditPlan: {
980
- kind: 'praxis.table.component-edit-plan',
981
- version: '1.0',
982
- componentId: 'praxis-table',
983
- operationId: 'rowAction.add',
984
- changeKind: 'add_row_action',
985
- capabilityPath: 'actions.row.actions[]',
986
- input: rowAction,
987
- value: rowAction,
988
- },
989
- explanation: `Vou criar um botão em cada linha para abrir ${surfaceLabel}.`,
990
- warnings: [
991
- clarificationKind === 'technical'
992
- ? 'selected-record-surface-row-action-continued-from-technical-clarification'
993
- : 'selected-record-surface-row-action-continued-from-generic-clarification',
994
- clarificationKind === 'technical'
995
- ? 'Residual continuity guard acted only after LLM asked for technical row-action details while declared recordSurfaces and conversation history already grounded the target surface.'
996
- : '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.',
997
- ],
998
- };
1239
+ return rowActionResponse;
1240
+ }
1241
+ selectedRecordSurfaceRowActionPlanForInfo(response, request) {
1242
+ if (response.type !== 'info' || !request)
1243
+ return null;
1244
+ const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
1245
+ const normalizedConversation = this.normalizeLabel([
1246
+ ...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
1247
+ request.prompt ?? '',
1248
+ ].join(' '));
1249
+ if (!this.textMentionsRowActionRequest(`${normalizedPrompt} ${normalizedConversation}`)) {
1250
+ return null;
1251
+ }
1252
+ const responseText = this.normalizeLabel([
1253
+ response.message ?? '',
1254
+ response.explanation ?? '',
1255
+ ].join(' '));
1256
+ if (!this.textMentionsSurfaceOpenCommitment(responseText) && !this.textMentionsRowActionRequest(responseText)) {
1257
+ return null;
1258
+ }
1259
+ const contextHints = this.contextHintsFor(request);
1260
+ const surfaces = this.selectedRecordSurfaces(contextHints);
1261
+ if (!surfaces.length)
1262
+ return null;
1263
+ const ranked = surfaces
1264
+ .map((surface) => ({
1265
+ surface,
1266
+ score: this.selectedRecordSurfacePromptScore(`${normalizedConversation || normalizedPrompt} ${responseText}`, surface),
1267
+ }))
1268
+ .filter((entry) => entry.score >= 2)
1269
+ .sort((left, right) => right.score - left.score);
1270
+ if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
1271
+ return null;
1272
+ }
1273
+ // Residual continuity guard: this runs only after the LLM has already
1274
+ // answered an info turn for a row-button request but failed to emit the
1275
+ // manifest-backed rowAction.add envelope. The target is ranked from the
1276
+ // governed recordSurfaces catalog plus LLM-authored evidence.
1277
+ return this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, 'selected-record-surface-row-action-info-continuity-guard', 'selected-record-surface-row-action-continued-from-info', 'Residual continuity guard acted only after LLM resolved a declared recordSurface in an info answer for a row-action request without emitting rowAction.add.');
999
1278
  }
1000
1279
  selectedRecordSurfaceRuntimeOperationForInfo(response, request) {
1001
1280
  if (response.type !== 'info' || !request)
@@ -1062,9 +1341,14 @@ class TableAgenticAuthoringTurnFlow {
1062
1341
  if (!request || !this.responseMayContainExecutableEnvelope(response))
1063
1342
  return null;
1064
1343
  const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
1065
- 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))
1066
1350
  return null;
1067
- if (this.promptRequestsSimilarRecords(normalizedPrompt))
1351
+ if (this.promptRequestsSimilarRecords(requestText))
1068
1352
  return null;
1069
1353
  const contextHints = this.contextHintsFor(request);
1070
1354
  const surfaces = this.selectedRecordSurfaces(contextHints);
@@ -1080,13 +1364,18 @@ class TableAgenticAuthoringTurnFlow {
1080
1364
  const ranked = surfaces
1081
1365
  .map((surface) => ({
1082
1366
  surface,
1083
- score: this.selectedRecordSurfacePromptScore(`${normalizedPrompt} ${responseEvidence}`, surface),
1367
+ score: this.selectedRecordSurfacePromptScore(`${requestText} ${responseEvidence}`, surface),
1084
1368
  }))
1085
1369
  .filter((entry) => entry.score >= 2)
1086
1370
  .sort((left, right) => right.score - left.score);
1087
1371
  if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
1088
1372
  return null;
1089
1373
  }
1374
+ if (this.textMentionsRowActionRequest(requestText)) {
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.');
1376
+ if (rowActionResponse)
1377
+ return rowActionResponse;
1378
+ }
1090
1379
  const surface = ranked[0].surface;
1091
1380
  const surfaceId = this.stringValue(surface['id']);
1092
1381
  if (!surfaceId)
@@ -1121,6 +1410,152 @@ class TableAgenticAuthoringTurnFlow {
1121
1410
  ],
1122
1411
  };
1123
1412
  }
1413
+ buildSelectedRecordSurfaceRowActionResponse(surface, source, warning, warningDetail) {
1414
+ const resourceSurface = this.toRecord(surface['resourceSurface']);
1415
+ if (!this.isResourceSurfaceCatalogDigest(resourceSurface)) {
1416
+ return null;
1417
+ }
1418
+ const surfaceId = this.stringValue(surface['id']) || this.stringValue(resourceSurface['id']);
1419
+ if (!surfaceId)
1420
+ return null;
1421
+ const surfaceLabel = this.stringValue(surface['label'])
1422
+ || this.stringValue(resourceSurface['title'])
1423
+ || this.humanizeField(surfaceId);
1424
+ const actionId = `open-${this.slugifyActionId(surfaceId)}`;
1425
+ const rowAction = {
1426
+ id: actionId,
1427
+ label: surfaceLabel,
1428
+ action: 'surface.open',
1429
+ icon: 'open_in_new',
1430
+ recordSurface: this.toAiJsonObject(resourceSurface),
1431
+ };
1432
+ return {
1433
+ type: 'patch',
1434
+ componentEditPlan: {
1435
+ kind: 'praxis.table.component-edit-plan',
1436
+ version: '1.0',
1437
+ componentId: 'praxis-table',
1438
+ operationId: 'rowAction.add',
1439
+ changeKind: 'add_row_action',
1440
+ capabilityPath: 'actions.row.actions[]',
1441
+ input: rowAction,
1442
+ value: rowAction,
1443
+ source,
1444
+ },
1445
+ explanation: `Vou criar um botão em cada linha para abrir ${surfaceLabel}.`,
1446
+ warnings: [warning, warningDetail],
1447
+ };
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
+ }
1124
1559
  selectedRecordSurfaceRowActionClarificationKind(response) {
1125
1560
  const text = this.normalizeLabel([
1126
1561
  response.message ?? '',
@@ -1134,6 +1569,30 @@ class TableAgenticAuthoringTurnFlow {
1134
1569
  'mais detalhes sobre surface',
1135
1570
  'mais detalhes sobre button',
1136
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',
1137
1596
  ].some((needle) => text.includes(needle))) {
1138
1597
  return 'technical';
1139
1598
  }
@@ -1150,16 +1609,68 @@ class TableAgenticAuthoringTurnFlow {
1150
1609
  }
1151
1610
  return null;
1152
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
+ }
1153
1633
  textMentionsRowActionRequest(normalizedText) {
1154
1634
  return [
1155
1635
  'botao',
1156
1636
  'acao de linha',
1157
1637
  'acoes de linha',
1638
+ 'acao por registro',
1639
+ 'acoes por registro',
1158
1640
  'nas linhas',
1159
1641
  'por linha',
1160
1642
  'em cada linha',
1161
1643
  ].some((needle) => normalizedText.includes(needle));
1162
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
+ }
1163
1674
  textMentionsRecordSurfaceOpenRequest(normalizedText) {
1164
1675
  return [
1165
1676
  'abrir',
@@ -1979,6 +2490,9 @@ class TableAgenticAuthoringTurnFlow {
1979
2490
  case 'rowAction.add':
1980
2491
  case 'add_row_action':
1981
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.`;
1982
2496
  case 'export.configure':
1983
2497
  return this.describeExportConfigure(input);
1984
2498
  case 'appearance.density.set':
@@ -3004,6 +3518,14 @@ class TableAgenticAuthoringTurnFlow {
3004
3518
  return {};
3005
3519
  }
3006
3520
  }
3521
+ cloneJson(value) {
3522
+ try {
3523
+ return JSON.parse(JSON.stringify(value));
3524
+ }
3525
+ catch {
3526
+ return value;
3527
+ }
3528
+ }
3007
3529
  toRecord(value) {
3008
3530
  return value && typeof value === 'object' && !Array.isArray(value)
3009
3531
  ? value