dsh-data-cleaning-agent 0.9.6 → 0.9.8

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.
package/lib/client.js CHANGED
@@ -157,12 +157,7 @@ window.__ModuleLoader__.load({
157
157
  box-sizing: border-box; display: flex; flex-direction: column;
158
158
  width: 100%; height: 100%; min-width: 0; min-height: 0; overflow: hidden;
159
159
  background: var(--dc-surface); color: var(--dc-text);
160
- container-type: inline-size;
161
- }
162
- @container (max-width: 520px) {
163
- .dcAgentRulesGrid, .dcAgentMappingRow { grid-template-columns: 1fr; }
164
- .dcAgentMappingRow > b { display: none; }
165
- .dcAgentCandidate { grid-template-columns: 1fr; }
160
+ container: dc-agent-workbench / inline-size;
166
161
  }
167
162
  .dcAgentCapabilities {
168
163
  display: flex;
@@ -515,8 +510,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
515
510
 
516
511
  .dcAgentWbBody {
517
512
  flex: 1 1 auto;
513
+ display: flex;
514
+ flex-direction: column;
515
+ gap: 14px;
518
516
  overflow: auto;
519
517
  padding: 18px;
518
+ background: var(--dc-page);
519
+ scrollbar-gutter: stable;
520
520
  }
521
521
 
522
522
  .dcAgentPane { display: flex; flex-direction: column; gap: 14px; }
@@ -569,7 +569,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
569
569
 
570
570
  .dcAgentGrid {
571
571
  display: grid;
572
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
572
+ grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
573
573
  gap: 10px;
574
574
  }
575
575
 
@@ -718,10 +718,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
718
718
  color: var(--dc-text);
719
719
  }
720
720
  .dcAgentHistoryTask { border: 1px solid var(--dc-border); border-radius: 6px; background: var(--dc-surface); color: inherit; font: inherit; cursor: pointer; }
721
- .dcAgentWizardNav { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-bottom: 12px; }
722
- .dcAgentWizardStep { display: flex; align-items: center; gap: 6px; padding: 7px; border: 1px solid var(--dc-border); border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 12px; cursor: pointer; }
721
+ .dcAgentWizardNav { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 16px; padding-bottom: 10px; border-bottom: 1px solid var(--dc-border); }
722
+ .dcAgentWizardStep { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 6px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: transparent; color: var(--dc-muted); font: inherit; font-size: 12px; cursor: pointer; }
723
723
  .dcAgentWizardStep b { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--dc-border); color: var(--dc-muted); }
724
- .dcAgentWizardStep.is-active { border-color: var(--dc-action); background: var(--dc-selected); color: var(--dc-action); }
724
+ .dcAgentWizardStep.is-active { border-bottom-color: var(--dc-action); background: transparent; color: var(--dc-action); }
725
725
  .dcAgentWizardStep.is-active b { background: var(--dc-action); color: var(--dc-on-action); }
726
726
  .dcAgentWizardPane { min-height: 230px; }
727
727
  .dcAgentWizardPane h4 { margin: 0 0 3px; font-size: 14px; }
@@ -730,14 +730,158 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
730
730
  .dcAgentPromptRules label { font-size: 12px; }
731
731
  .dcAgentPromptPreview { max-height: 260px; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 8px; background: var(--dc-page); white-space: pre-wrap; word-break: break-word; font: inherit; font-size: 12px; line-height: 18px; }
732
732
  .dcAgentFormField { display: grid; gap: 6px; font-size: 12px; font-weight: 600; }
733
- .dcAgentMappingRow { display: grid; grid-template-columns: minmax(0, 1fr) 24px minmax(180px, 1fr); align-items: center; gap: 8px; margin-top: 7px; font-size: 12px; }
734
- .dcAgentMappingRow > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
735
- .dcAgentMappingRow > b { text-align: center; color: light-dark(#8792a3, #8290a5); }
736
- .dcAgentMappingPicker summary { cursor: pointer; overflow-wrap: anywhere; }
737
- .dcAgentMappingPicker[open] { padding: 8px; border: 1px solid var(--dc-border); border-radius: 8px; }
738
- .dcAgentMappingPicker input, .dcAgentMappingPicker select { width: 100%; margin-top: 8px; max-width: 100%; min-width: 0; }
733
+ .dcAgentMappingSection { padding: 0; }
734
+ .dcAgentMappingHeader {
735
+ display: grid;
736
+ grid-template-columns: minmax(0, 1fr) auto;
737
+ align-items: start;
738
+ gap: 12px 20px;
739
+ padding: 14px 16px;
740
+ border-bottom: 1px solid var(--dc-border);
741
+ }
742
+ .dcAgentMappingHeader h3 { margin: 0; font-size: 14px; }
743
+ .dcAgentMappingHeader .dcAgentHint { margin-top: 4px; }
744
+ .dcAgentMappingProgress {
745
+ display: inline-flex;
746
+ align-items: baseline;
747
+ gap: 4px;
748
+ min-width: 108px;
749
+ padding: 8px 10px;
750
+ border: 1px solid var(--dc-border);
751
+ border-radius: 6px;
752
+ background: var(--dc-page);
753
+ color: var(--dc-muted);
754
+ font-size: 12px;
755
+ white-space: nowrap;
756
+ }
757
+ .dcAgentMappingProgress b { color: var(--dc-action); font-size: 16px; font-variant-numeric: tabular-nums; }
758
+ .dcAgentMappingToolbar {
759
+ display: flex;
760
+ align-items: center;
761
+ justify-content: space-between;
762
+ gap: 10px 16px;
763
+ padding: 10px 16px;
764
+ border-bottom: 1px solid var(--dc-border);
765
+ background: var(--dc-page);
766
+ }
767
+ .dcAgentMappingLegend { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; color: var(--dc-muted); font-size: 12px; }
768
+ .dcAgentMappingLegendItem { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
769
+ .dcAgentMappingLegendItem::before { width: 7px; height: 7px; border-radius: 50%; background: var(--dc-border); content: ''; }
770
+ .dcAgentMappingLegendItem[data-status="confirmed"]::before { background: light-dark(#15803d, #86efac); }
771
+ .dcAgentMappingLegendItem[data-status="review"]::before { background: light-dark(#a16207, #fcd34d); }
772
+ .dcAgentMappingLegendItem[data-status="unmatched"]::before { background: light-dark(#b91c1c, #fca5a5); }
773
+ .dcAgentMappingAuto { flex: 0 0 auto; padding: 6px 10px; font-size: 12px; }
774
+ .dcAgentMappingNote { padding: 10px 16px; border-bottom: 1px solid var(--dc-border); }
775
+ .dcAgentMappingListHead,
776
+ .dcAgentMappingSummary,
777
+ .dcAgentMappingMeta {
778
+ display: grid;
779
+ grid-template-columns: minmax(120px, 0.8fr) 24px minmax(220px, 1.2fr);
780
+ align-items: center;
781
+ gap: 8px;
782
+ }
783
+ .dcAgentMappingListHead {
784
+ padding: 8px 16px;
785
+ border-bottom: 1px solid var(--dc-border);
786
+ background: var(--dc-page);
787
+ color: var(--dc-muted);
788
+ font-size: 11px;
789
+ font-weight: 600;
790
+ }
791
+ .dcAgentMappingListHead > span:last-child { grid-column: 3; }
792
+ .dcAgentMappingRow { font-size: 12px; }
793
+ .dcAgentMappingRow + .dcAgentMappingRow { border-top: 1px solid var(--dc-border); }
794
+ .dcAgentMappingPicker { display: block; }
795
+ .dcAgentMappingSummary {
796
+ min-height: 52px;
797
+ padding: 8px 16px;
798
+ cursor: pointer;
799
+ list-style: none;
800
+ }
801
+ .dcAgentMappingSummary::-webkit-details-marker { display: none; }
802
+ .dcAgentMappingSummary:hover { background: var(--dc-page); }
803
+ .dcAgentMappingPicker[open] > .dcAgentMappingSummary { background: var(--dc-selected); }
804
+ .dcAgentMappingSource { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
805
+ .dcAgentMappingSummary > b { text-align: center; color: light-dark(#8792a3, #8290a5); }
806
+ .dcAgentMappingValue {
807
+ display: flex;
808
+ align-items: center;
809
+ justify-content: space-between;
810
+ gap: 10px;
811
+ min-width: 0;
812
+ min-height: 36px;
813
+ padding: 7px 10px;
814
+ border: 1px solid var(--dc-border);
815
+ border-radius: 6px;
816
+ background: var(--dc-surface);
817
+ font-size: 14px;
818
+ font-weight: 500;
819
+ overflow-wrap: anywhere;
820
+ }
821
+ .dcAgentMappingValue::after {
822
+ flex: 0 0 auto;
823
+ width: 7px;
824
+ height: 7px;
825
+ border-right: 1.5px solid currentColor;
826
+ border-bottom: 1.5px solid currentColor;
827
+ content: '';
828
+ transform: rotate(45deg) translate(-2px, 2px);
829
+ transition: transform 120ms ease;
830
+ }
831
+ .dcAgentMappingPicker[open] .dcAgentMappingValue::after { transform: rotate(225deg); }
832
+ .dcAgentMappingPickerPanel {
833
+ display: grid;
834
+ gap: 8px;
835
+ padding: 12px 16px 14px;
836
+ border-top: 1px solid var(--dc-border);
837
+ border-bottom: 1px solid var(--dc-border);
838
+ background: var(--dc-page);
839
+ }
840
+ .dcAgentMappingPickerFilters { display: grid; grid-template-columns: minmax(0, 1fr) minmax(150px, 0.42fr); gap: 8px; }
841
+ .dcAgentMappingPickerPanel .dcAgentField { box-sizing: border-box; width: 100%; max-width: 100%; min-width: 0; margin: 0; }
842
+ .dcAgentMappingOptionGroups {
843
+ max-height: 286px;
844
+ overflow: auto;
845
+ padding-right: 4px;
846
+ scrollbar-gutter: stable;
847
+ }
848
+ .dcAgentMappingClearOption,
849
+ .dcAgentMappingOption {
850
+ display: flex;
851
+ align-items: center;
852
+ gap: 8px;
853
+ min-width: 0;
854
+ min-height: 36px;
855
+ padding: 6px 8px;
856
+ border-radius: 4px;
857
+ cursor: pointer;
858
+ }
859
+ .dcAgentMappingClearOption {
860
+ position: sticky;
861
+ top: 0;
862
+ z-index: 1;
863
+ border-bottom: 1px solid var(--dc-border);
864
+ border-radius: 0;
865
+ background: var(--dc-page);
866
+ color: var(--dc-muted);
867
+ }
868
+ .dcAgentMappingOption:hover,
869
+ .dcAgentMappingOption:has(input:checked) { background: var(--dc-selected); color: var(--dc-action); }
870
+ .dcAgentMappingOption:has(input:disabled) { opacity: 0.52; cursor: not-allowed; }
871
+ .dcAgentMappingClearOption input,
872
+ .dcAgentMappingOption input { flex: 0 0 auto; width: 16px; height: 16px; margin: 0; accent-color: var(--dc-action); }
873
+ .dcAgentMappingOption > span { min-width: 0; overflow-wrap: anywhere; }
874
+ .dcAgentMappingRow .dcAgentMappingOption > small { margin: 0 0 0 auto; color: var(--dc-muted); white-space: nowrap; }
875
+ .dcAgentMappingOptionGroup { margin-top: 12px; }
876
+ .dcAgentMappingOptionGroupTitle { margin: 0 0 6px; color: var(--dc-muted); font-size: 12px; font-weight: 600; }
877
+ .dcAgentMappingOptionGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 4px 8px; }
878
+ .dcAgentMappingCurrentNotice { margin: 8px 0 0; padding: 7px 8px; background: var(--dc-surface); color: var(--dc-muted); font-size: 12px; }
879
+ .dcAgentMappingMatch { margin: 0; }
880
+ .dcAgentMappingMeta { padding: 7px 16px 10px; }
881
+ .dcAgentMappingMeta > * { grid-column: 3; min-width: 0; }
882
+ .dcAgentMappingMeta > .dcAgentHint { margin: 0; }
739
883
  .dcAgentMappingRow .dcAgentButton { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
740
- .dcAgentMappingRow small { display: block; margin: 4px 0; overflow-wrap: anywhere; }
884
+ .dcAgentMappingRow small { display: block; margin: 0; overflow-wrap: anywhere; }
741
885
  .dcAgentMappingStatus[data-status="confirmed"] { color: light-dark(#15803d, #86efac); }
742
886
  .dcAgentMappingStatus[data-status="review"] { color: light-dark(#a16207, #fcd34d); }
743
887
  .dcAgentMappingStatus[data-status="unmatched"] { color: light-dark(#b91c1c, #fca5a5); }
@@ -786,8 +930,175 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
786
930
  .dcAgentPreviewTable > .dcAgentTable th {
787
931
  position: sticky; top: 0; z-index: 2; background: var(--dc-table-head);
788
932
  }
933
+ .dcAgentDatasetPreview {
934
+ padding: 0;
935
+ overflow: hidden;
936
+ }
937
+ .dcAgentDatasetHeader {
938
+ display: flex;
939
+ align-items: baseline;
940
+ justify-content: space-between;
941
+ gap: 8px 16px;
942
+ padding: 14px 16px 12px;
943
+ border-bottom: 1px solid var(--dc-border);
944
+ }
945
+ .dcAgentDatasetHeader h3 { margin: 0; font-size: 14px; }
946
+ .dcAgentDatasetHeader .dcAgentHint { text-align: right; }
947
+ .dcAgentDatasetPreview > .dcAgentPreviewTable {
948
+ margin: 0;
949
+ border: 0;
950
+ border-radius: 0;
951
+ }
952
+ .dcAgentDatasetPreview > .dcAgentPreviewTable > .dcAgentTable :is(th, td):last-child { border-right: 0; }
953
+ .dcAgentDatasetPreview > .dcAgentPreviewTable > .dcAgentTable tbody tr:last-child > td { border-bottom: 0; }
954
+ .dcAgentDatasetPreview > .dcAgentPreviewTable > .dcAgentTable :is(th, td):first-child {
955
+ width: 72px;
956
+ min-width: 72px;
957
+ max-width: 72px;
958
+ text-align: center;
959
+ color: var(--dc-muted);
960
+ font-variant-numeric: tabular-nums;
961
+ }
962
+ .dcAgentDatasetFooter {
963
+ display: grid;
964
+ grid-template-columns: minmax(0, 1fr) auto;
965
+ align-items: center;
966
+ gap: 10px 16px;
967
+ padding: 12px 16px;
968
+ border-top: 1px solid var(--dc-border);
969
+ background: var(--dc-page);
970
+ }
971
+ .dcAgentDatasetPagination,
972
+ .dcAgentDatasetDownloads {
973
+ display: flex;
974
+ align-items: center;
975
+ gap: 8px;
976
+ min-width: 0;
977
+ }
978
+ .dcAgentDatasetDownloads { justify-content: flex-end; }
979
+ .dcAgentDatasetPreview .dcAgentButton {
980
+ display: inline-flex;
981
+ align-items: center;
982
+ justify-content: center;
983
+ gap: 6px;
984
+ min-height: 32px;
985
+ padding: 6px 10px;
986
+ line-height: 18px;
987
+ }
988
+ .dcAgentDatasetPage {
989
+ min-width: 72px;
990
+ padding: 6px 8px;
991
+ border: 1px solid var(--dc-border);
992
+ border-radius: 6px;
993
+ background: var(--dc-surface);
994
+ font-size: 12px;
995
+ font-variant-numeric: tabular-nums;
996
+ font-weight: 600;
997
+ text-align: center;
998
+ white-space: nowrap;
999
+ }
1000
+ .dcAgentDatasetFooterNote {
1001
+ grid-column: 1 / -1;
1002
+ padding-top: 10px;
1003
+ border-top: 1px solid var(--dc-border);
1004
+ font-size: 12px;
1005
+ line-height: 1.55;
1006
+ }
1007
+ .dcAgentIntakeSummary { padding: 0; overflow: hidden; }
1008
+ .dcAgentIntakeSummaryHead {
1009
+ padding: 14px 16px 10px;
1010
+ }
1011
+ .dcAgentIntakeSummaryHead > div { min-width: 0; }
1012
+ .dcAgentIntakeSummaryHead h3 { margin-bottom: 4px; }
1013
+ .dcAgentIntakeSummary > .dcAgentHint { padding: 0 16px 14px; }
1014
+ .dcAgentIntakeEditor { padding: 0; overflow: hidden; }
1015
+ .dcAgentIntakeEditorHeader { padding: 14px 16px 10px; border-bottom: 1px solid var(--dc-border); }
1016
+ .dcAgentIntakeEditorHeader h3 { margin-bottom: 4px; }
1017
+ .dcAgentIntakeSourceActions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 12px 16px; }
1018
+ .dcAgentIntakePaste { padding: 0 16px 12px; }
1019
+ .dcAgentIntakePaste .dcAgentTextarea { min-height: 116px; }
1020
+ .dcAgentIntakeEditorActions { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-top: 1px solid var(--dc-border); }
1021
+ .dcAgentUploadActions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
789
1022
  .dcAgentHistoryTask { display: block; width: 100%; padding: 10px; text-align: left; }
790
1023
  .dcAgentHistoryTask h3 { margin: 0; font-size: 12px; }
1024
+ .dcAgentViewTabs {
1025
+ display: flex;
1026
+ align-items: center;
1027
+ gap: 20px;
1028
+ min-height: 34px;
1029
+ padding: 0 4px;
1030
+ border-bottom: 1px solid var(--dc-border);
1031
+ }
1032
+ .dcAgentViewTabs .dcAgentButton {
1033
+ align-self: stretch;
1034
+ padding: 6px 2px;
1035
+ border: 0;
1036
+ border-bottom: 2px solid transparent;
1037
+ border-radius: 0;
1038
+ background: transparent;
1039
+ color: var(--dc-muted);
1040
+ font-size: 12px;
1041
+ }
1042
+ .dcAgentViewTabs .dcAgentButton[aria-pressed="true"] { border-bottom-color: var(--dc-action); color: var(--dc-action); font-weight: 600; }
1043
+ .dcAgentProfilePane { flex: 1 0 auto; min-height: 320px; }
1044
+ .dcAgentProfileSurface {
1045
+ flex: 1 0 auto;
1046
+ display: flex;
1047
+ flex-direction: column;
1048
+ min-height: 320px;
1049
+ padding: 0;
1050
+ overflow: hidden;
1051
+ }
1052
+ .dcAgentProfileHeader { padding: 14px 16px; border-bottom: 1px solid var(--dc-border); }
1053
+ .dcAgentProfileHeader h3 { margin: 0; font-size: 14px; }
1054
+ .dcAgentProfileHeader .dcAgentHint { margin-top: 4px; }
1055
+ .dcAgentProfileReport { flex: 1 0 auto; display: flex; flex-direction: column; gap: 14px; padding: 14px 16px; }
1056
+ .dcAgentProfileMetrics { grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); }
1057
+ .dcAgentProfileMetrics .dcAgentCard { padding: 10px 12px; background: var(--dc-page); }
1058
+ .dcAgentProfileMetrics .dcAgentCard b { font-size: 17px; }
1059
+ .dcAgentProfileTableSection { min-width: 0; }
1060
+ .dcAgentProfileTableHead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
1061
+ .dcAgentProfileTableHead h4 { margin: 0; font-size: 13px; }
1062
+ .dcAgentProfileTableWrap { max-width: 100%; max-height: 360px; overflow: auto; border: 1px solid var(--dc-border); border-radius: 6px; scrollbar-gutter: stable; }
1063
+ .dcAgentProfileTableWrap .dcAgentTable { min-width: 420px; border-collapse: separate; border-spacing: 0; }
1064
+ .dcAgentProfileTableWrap .dcAgentTable th { position: sticky; top: 0; z-index: 1; }
1065
+ .dcAgentProfileTableWrap .dcAgentTable :is(th, td):last-child { border-right: 0; }
1066
+ .dcAgentProfileTableWrap .dcAgentTable tbody tr:last-child > td { border-bottom: 0; }
1067
+ .dcAgentProfileTableWrap .dcAgentTable :is(th, td):not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }
1068
+ .dcAgentProfileActions { display: flex; justify-content: flex-end; margin-top: auto; padding-top: 14px; border-top: 1px solid var(--dc-border); }
1069
+ .dcAgentProfileEmpty { flex: 1 0 auto; display: flex; align-items: center; justify-content: center; padding: 24px 16px; }
1070
+ .dcAgentMatchPane { flex: 0 0 auto; }
1071
+ .dcAgentMatchSurface {
1072
+ flex: 0 0 auto;
1073
+ display: flex;
1074
+ flex-direction: column;
1075
+ padding: 0;
1076
+ overflow: hidden;
1077
+ }
1078
+ .dcAgentMatchHeader { padding: 14px 16px; border-bottom: 1px solid var(--dc-border); }
1079
+ .dcAgentMatchHeader h3 { margin: 0; font-size: 14px; }
1080
+ .dcAgentMatchHeader .dcAgentHint { margin-top: 4px; }
1081
+ .dcAgentMatchContent {
1082
+ display: grid;
1083
+ grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
1084
+ align-items: stretch;
1085
+ }
1086
+ .dcAgentMatchBlock { min-width: 0; padding: 16px; }
1087
+ .dcAgentMatchBlock + .dcAgentMatchBlock { border-left: 1px solid var(--dc-border); }
1088
+ .dcAgentMatchBlockHead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
1089
+ .dcAgentMatchBlockHead h4 { margin: 0; font-size: 13px; }
1090
+ .dcAgentMatchBlock .dcAgentChips { margin-bottom: 8px; }
1091
+ .dcAgentMatchBlock .dcAgentHint + .dcAgentHint { margin-top: 6px; }
1092
+ .dcAgentMatchScope {
1093
+ display: inline-flex;
1094
+ align-items: baseline;
1095
+ gap: 5px;
1096
+ color: var(--dc-muted);
1097
+ font-size: 12px;
1098
+ white-space: nowrap;
1099
+ }
1100
+ .dcAgentMatchScope b { color: var(--dc-text); font-size: 14px; font-variant-numeric: tabular-nums; }
1101
+ .dcAgentMatchActions { display: flex; justify-content: flex-end; margin: 0 16px; padding: 14px 0; border-top: 1px solid var(--dc-border); }
791
1102
  @media (max-width: 760px) {
792
1103
  .dcAgentQccBadge, .dcAgentJobsPill { display: none; }
793
1104
  .dcAgentCandidate { grid-template-columns: 1fr; }
@@ -797,8 +1108,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
797
1108
  .dcAgentPromptPanel { width: 100%; height: 100dvh; max-height: 100dvh; border-radius: 0; }
798
1109
  .dcAgentWizardNav { grid-template-columns: repeat(2, 1fr); }
799
1110
  .dcAgentRulesGrid { grid-template-columns: 1fr; }
800
- .dcAgentMappingRow { grid-template-columns: 1fr; }
801
- .dcAgentMappingRow > b { display: none; }
1111
+ .dcAgentMappingListHead { display: none; }
1112
+ .dcAgentMappingSummary, .dcAgentMappingMeta { grid-template-columns: 1fr; }
1113
+ .dcAgentMappingSummary > b { display: none; }
1114
+ .dcAgentMappingPickerFilters { grid-template-columns: 1fr; }
1115
+ .dcAgentMappingMeta > * { grid-column: 1; }
802
1116
  }
803
1117
  @media (min-width: 761px) and (max-width: 1040px) {
804
1118
  .dcAgentQccBadge, .dcAgentJobsPill { display: none; }
@@ -820,6 +1134,58 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
820
1134
  .dcAgentTable th, .dcAgentTable td { height: 44px; box-sizing: border-box; font-size: 13px; }
821
1135
  .dcAgentTable tbody tr:hover { background: var(--dc-selected); }
822
1136
  .dcAgentSection, .dcAgentCard { background: var(--dc-surface); }
1137
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] {
1138
+ display: grid;
1139
+ grid-template-columns: minmax(0, 1fr) auto auto;
1140
+ align-items: center;
1141
+ gap: 6px 10px;
1142
+ padding: 14px 16px;
1143
+ }
1144
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > h3 {
1145
+ grid-column: 1;
1146
+ grid-row: 1;
1147
+ min-width: 0;
1148
+ margin: 0;
1149
+ overflow-wrap: anywhere;
1150
+ font-size: 14px;
1151
+ }
1152
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > b {
1153
+ grid-column: 2;
1154
+ grid-row: 1;
1155
+ padding: 2px 8px;
1156
+ border: 1px solid var(--dc-border);
1157
+ border-radius: 4px;
1158
+ background: var(--dc-page);
1159
+ color: var(--dc-muted);
1160
+ font-size: 12px;
1161
+ font-weight: 600;
1162
+ white-space: nowrap;
1163
+ }
1164
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > details {
1165
+ grid-column: 3;
1166
+ grid-row: 1;
1167
+ color: var(--dc-muted);
1168
+ font-size: 12px;
1169
+ }
1170
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > :is(p, progress) { grid-column: 1 / -1; }
1171
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > p { margin: 0; }
1172
+ .dcAgentWbBody > .dcAgentSection[role="status"]:not([aria-label]) {
1173
+ min-height: 156px;
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ align-items: center;
1177
+ justify-content: center;
1178
+ gap: 12px;
1179
+ border: 0;
1180
+ background: transparent;
1181
+ text-align: center;
1182
+ }
1183
+ .dcAgentWbBody > .dcAgentSection[role="status"]:not([aria-label]) > p {
1184
+ max-width: 440px;
1185
+ margin: 0;
1186
+ color: var(--dc-muted);
1187
+ }
1188
+ .dcAgentPane > .dcAgentArtifactList { margin-top: 0; }
823
1189
  .dcAgentFieldGroup { background: var(--dc-page); padding: 12px; border-radius: 8px; }
824
1190
  .dcAgentPromptFieldGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; margin-top: 10px; }
825
1191
  .dcAgentPromptFieldGrid .dcAgentPromptChoice { border: 0; border-radius: 4px; min-width: 0; align-items: flex-start; font-size: 13px; }
@@ -830,6 +1196,66 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
830
1196
  .dcAgentPromptAction.is-primary:hover:not(:disabled) { background: var(--dc-hover); }
831
1197
  :is(.dcAgentWorkbenchContent, .dcAgentPromptPanel, .dcAgentCapabilities, .dcAgentPromptLayer) :is(button, input, select, textarea, summary):focus-visible,
832
1198
  .dcAgentLauncher:focus-visible { outline: 2px solid var(--dc-action); outline-offset: 2px; }
1199
+ @container dc-agent-workbench (max-width: 640px) {
1200
+ .dcAgentRulesGrid { grid-template-columns: 1fr; }
1201
+ .dcAgentCandidate { grid-template-columns: 1fr; }
1202
+ }
1203
+ @container dc-agent-workbench (max-width: 570px) {
1204
+ .dcAgentGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1205
+ .dcAgentGrid > .dcAgentCard:last-child:nth-child(odd) { grid-column: 1 / -1; }
1206
+ }
1207
+ @container dc-agent-workbench (max-width: 520px) {
1208
+ .dcAgentMappingHeader { grid-template-columns: 1fr; }
1209
+ .dcAgentMappingProgress { justify-self: start; }
1210
+ .dcAgentMappingToolbar { align-items: flex-start; flex-direction: column; }
1211
+ .dcAgentMappingListHead { display: none; }
1212
+ .dcAgentMappingSummary, .dcAgentMappingMeta { grid-template-columns: 1fr; }
1213
+ .dcAgentMappingSummary > b { display: none; }
1214
+ .dcAgentMappingPickerFilters { grid-template-columns: 1fr; }
1215
+ .dcAgentMappingMeta > * { grid-column: 1; }
1216
+ .dcAgentStep { gap: 6px; padding: 10px 3px; font-size: 11px; }
1217
+ .dcAgentStepIcon { width: 26px; height: 26px; }
1218
+ .dcAgentStepLabel {
1219
+ display: -webkit-box;
1220
+ min-height: 32px;
1221
+ line-height: 16px;
1222
+ white-space: normal;
1223
+ -webkit-box-orient: vertical;
1224
+ -webkit-line-clamp: 2;
1225
+ }
1226
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] { grid-template-columns: minmax(0, 1fr) auto; }
1227
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > h3 { grid-column: 1 / -1; }
1228
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > b { grid-column: 1; grid-row: 2; justify-self: start; }
1229
+ .dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > details { grid-column: 2; grid-row: 2; }
1230
+ .dcAgentDatasetHeader { display: grid; }
1231
+ .dcAgentDatasetHeader .dcAgentHint { text-align: left; }
1232
+ .dcAgentDatasetFooter { grid-template-columns: 1fr; }
1233
+ .dcAgentDatasetPagination,
1234
+ .dcAgentDatasetDownloads { justify-content: flex-start; flex-wrap: wrap; }
1235
+ .dcAgentDatasetFooterNote { grid-column: 1; }
1236
+ .dcAgentProfileHeader, .dcAgentProfileReport { padding-inline: 12px; }
1237
+ .dcAgentProfileMetrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1238
+ }
1239
+ @container dc-agent-workbench (max-width: 640px) {
1240
+ .dcAgentMatchContent { grid-template-columns: 1fr; }
1241
+ .dcAgentMatchBlock + .dcAgentMatchBlock { border-top: 1px solid var(--dc-border); border-left: 0; }
1242
+ }
1243
+ @container dc-agent-workbench (max-width: 420px) {
1244
+ .dcAgentIntakeSummaryHead { padding-inline: 12px; }
1245
+ .dcAgentIntakeSummary > .dcAgentHint,
1246
+ .dcAgentIntakeEditorHeader,
1247
+ .dcAgentIntakeSourceActions,
1248
+ .dcAgentIntakePaste,
1249
+ .dcAgentIntakeEditorActions { padding-inline: 12px; }
1250
+ .dcAgentIntakeSourceActions .dcAgentButton { flex: 1 1 100%; }
1251
+ .dcAgentUploadActions .dcAgentButton { flex: 1 1 100%; }
1252
+ .dcAgentMatchHeader, .dcAgentMatchBlock { padding-inline: 12px; }
1253
+ .dcAgentMatchActions { margin-inline: 12px; }
1254
+ .dcAgentMatchActions .dcAgentButton { width: 100%; }
1255
+ }
1256
+ @container dc-agent-workbench (max-width: 360px) {
1257
+ .dcAgentDatasetDownloads > .dcAgentButton { flex: 1 1 100%; }
1258
+ }
833
1259
  @media (max-width: 760px) {
834
1260
  [data-dc-agent-hero-title="true"] { font-size: 20px; }
835
1261
  .dcAgentHeroLogo { width: 40px; height: 40px; border-radius: 8px; }
@@ -1354,41 +1780,60 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1354
1780
  const occupied = (id) => isAnchorField(id) && mappings.some((item) => item.sourceField !== sourceField && item.targetField === id);
1355
1781
  const fields = groups.flatMap(([, , items]) => items);
1356
1782
  const label = fields.find(([id]) => id === value)?.[1] || '不参与匹配 / 补全';
1357
- const matches = (id, name) => normalizeMappingLabel([id, name, ...(MAPPING_ALIASES[id] || []), ...Object.entries(MAPPING_HINTS).filter(([, ids]) => ids.includes(id)).map(([hint]) => hint)].join(' ')).includes(normalizeMappingLabel(query));
1783
+ const normalizedQuery = normalizeMappingLabel(query);
1784
+ const matches = (id, name) => normalizeMappingLabel([id, name, ...(MAPPING_ALIASES[id] || []), ...Object.entries(MAPPING_HINTS).filter(([, ids]) => ids.includes(id)).map(([hint]) => hint)].join(' ')).includes(normalizedQuery);
1358
1785
  const filteredGroups = groups.filter(([id]) => !category || id === category)
1359
- .map(([id, title, items]) => [id, title, items.filter(([key, name]) => matches(key, name))])
1786
+ .map(([id, title, items, tool]) => {
1787
+ const groupMatches = normalizeMappingLabel([id, title, tool || ''].join(' ')).includes(normalizedQuery);
1788
+ return [id, title, groupMatches ? items : items.filter(([key, name]) => matches(key, name))];
1789
+ })
1360
1790
  .filter(([, , items]) => items.length);
1361
1791
  const filteredFields = filteredGroups.flatMap(([, , items]) => items);
1362
1792
  const hiddenSelection = value && !filteredFields.some(([id]) => id === value);
1793
+ const selectMapping = (event) => {
1794
+ onChange(sourceField, event.currentTarget.value);
1795
+ const details = event.currentTarget.closest('details');
1796
+ if (details) { details.open = false; details.querySelector('summary')?.focus(); }
1797
+ };
1363
1798
  return h('div', { className: 'dcAgentMappingRow' },
1364
- h('span', { title: sourceField }, sourceField), h('b', { 'aria-hidden': 'true' }, '→'),
1365
- h('div', { style: { minWidth: 0 } },
1366
- h('details', { className: 'dcAgentMappingPicker', onKeyDown: (event) => {
1799
+ h('details', { className: 'dcAgentMappingPicker', onKeyDown: (event) => {
1367
1800
  if (event.key === 'Escape' && event.currentTarget.open) {
1368
1801
  event.preventDefault(); event.stopPropagation();
1369
1802
  event.currentTarget.open = false; event.currentTarget.querySelector('summary')?.focus();
1370
1803
  }
1371
1804
  } },
1372
- h('summary', { className: 'dcAgentField', 'aria-label': `${sourceField} 当前映射` }, label),
1373
- h('input', { className: 'dcAgentField', value: query, placeholder: '模糊搜索字段名称 / 英文键 / 别名', 'aria-label': `${sourceField} 搜索映射字段`, onChange: (event) => setQuery(event.target.value) }),
1374
- h('select', { className: 'dcAgentField', value: category, 'aria-label': `${sourceField} 映射大类`, onChange: (event) => setCategory(event.target.value) },
1375
- h('option', { value: '' }, '全部维度'),
1376
- groups.map(([id, title]) => h('option', { key: id, value: id }, title))),
1377
- h('select', { className: 'dcAgentField', size: 8, style: { height: 'auto', minHeight: '180px' }, value, 'aria-label': `${sourceField} 字段映射`, onChange: (event) => {
1378
- onChange(sourceField, event.target.value);
1379
- const details = event.currentTarget.closest('details');
1380
- if (details) { details.open = false; details.querySelector('summary')?.focus(); }
1381
- } },
1382
- h('option', { value: '' }, '不参与匹配 / 补全'),
1383
- hiddenSelection ? h('option', { value, disabled: true }, `当前选择:${label}(不符合筛选,保持不变)`) : null,
1384
- filteredGroups.map(([id, title, items]) => h('optgroup', { key: id, label: title }, items.map(([key, name]) =>
1385
- h('option', { key, value: key, disabled: occupied(key) }, `${name}${occupied(key) ? '(已被其他列使用)' : ''}`)))),
1805
+ h('summary', { className: 'dcAgentMappingSummary', 'aria-label': `${sourceField} 当前映射` },
1806
+ h('span', { className: 'dcAgentMappingSource', title: sourceField }, sourceField),
1807
+ h('b', { 'aria-hidden': 'true' }, '→'),
1808
+ h('span', { className: 'dcAgentMappingValue' }, label),
1809
+ ),
1810
+ h('div', { className: 'dcAgentMappingPickerPanel' },
1811
+ h('div', { className: 'dcAgentMappingPickerFilters' },
1812
+ h('input', { className: 'dcAgentField', value: query, placeholder: '模糊搜索字段名称 / 英文键 / 别名', 'aria-label': `${sourceField} 搜索映射字段`, onChange: (event) => setQuery(event.target.value) }),
1813
+ h('select', { className: 'dcAgentField', value: category, 'aria-label': `${sourceField} 映射大类`, onChange: (event) => setCategory(event.target.value) },
1814
+ h('option', { value: '' }, '全部维度'),
1815
+ groups.map(([id, title]) => h('option', { key: id, value: id }, title))),
1386
1816
  ),
1387
- h('p', { className: 'dcAgentHint', role: 'status' }, filteredFields.length ? `匹配 ${filteredFields.length} 个字段;选择后采用。` : '无匹配字段;当前选择保持不变。'),
1817
+ h('div', { className: 'dcAgentMappingOptionGroups', role: 'radiogroup', 'aria-label': `${sourceField} 字段映射` },
1818
+ h('label', { className: 'dcAgentMappingClearOption' },
1819
+ h('input', { type: 'radio', name: `mapping:${sourceField}`, value: '', checked: !value, onChange: selectMapping }),
1820
+ h('span', null, '不参与匹配 / 补全')),
1821
+ hiddenSelection ? h('p', { className: 'dcAgentMappingCurrentNotice' }, `当前选择:${label}(不符合筛选,保持不变)`) : null,
1822
+ filteredGroups.map(([id, title, items]) => h('section', { key: id, className: 'dcAgentMappingOptionGroup', 'data-mapping-group': id },
1823
+ h('h4', { className: 'dcAgentMappingOptionGroupTitle' }, title),
1824
+ h('div', { className: 'dcAgentMappingOptionGrid' }, items.map(([key, name]) => h('label', { key, className: 'dcAgentMappingOption' },
1825
+ h('input', { type: 'radio', name: `mapping:${sourceField}`, value: key, checked: value === key, disabled: occupied(key), onChange: selectMapping }),
1826
+ h('span', null, name),
1827
+ occupied(key) ? h('small', null, '已占用') : null,
1828
+ ))),
1829
+ )),
1830
+ ),
1831
+ h('p', { className: 'dcAgentHint dcAgentMappingMatch', role: 'status' }, filteredFields.length ? `匹配 ${filteredFields.length} 个字段;选择后采用。` : '无匹配字段;当前选择保持不变。'),
1388
1832
  ),
1833
+ ),
1834
+ h('div', { className: 'dcAgentMappingMeta' },
1389
1835
  value === 'beneficial_owner_first_name' || value.startsWith('financial_') ? h('p', { className: 'dcAgentHint' }, value === 'beneficial_owner_first_name' ? '仅取返回首条受益所有人,不代表唯一或最大受益人。' : '取返回首个报告期,不跨期补值;报告期保留在结果取值说明中。') : null,
1390
- h('small', { className: 'dcAgentMappingStatus', 'data-status': value ? 'confirmed' : recommendation.candidates.length ? 'review' : 'unmatched',
1391
- style: { display: 'block', padding: '6px 0' } },
1836
+ h('small', { className: 'dcAgentMappingStatus', 'data-status': value ? 'confirmed' : recommendation.candidates.length ? 'review' : 'unmatched' },
1392
1837
  value ? `${recommendation.automatic && recommendation.candidates[0]?.[0] === value ? '自动通过' : '已确认'}:${label};可手动调整` : recommendation.candidates.length ? `待人工确认:${recommendation.conflict ? '重复列可确认使用同一补全字段;主体标识只选一列' : recommendation.reason}` : '未匹配:保留原列,可手动选择字段'),
1393
1838
  !value && recommendation.candidates.length ? h('div', { className: 'dcAgentRow', 'aria-label': `${sourceField} 推荐映射` }, recommendation.candidates.map(([id, name]) =>
1394
1839
  h('button', { key: id, type: 'button', className: 'dcAgentButton', disabled: occupied(id), onClick: () => onChange(sourceField, id) }, `推荐:${name}${occupied(id) ? '(已占用)' : ''}`))) : null,
@@ -1496,8 +1941,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1496
1941
  const time = date && Number.isFinite(date.getTime()) ? date.toLocaleString() : '';
1497
1942
  return `${base}|${task.source?.type === 'image' && task.state === 'draft' ? '待识别' : `${task.source?.rowCount ?? 0} 条`}${time ? '|' + time : ''}`;
1498
1943
  }
1944
+ function taskBelongsToSession(task, sessionId) {
1945
+ if (!sessionId || task?.originSessionId !== sessionId) return false;
1946
+ const scope = sessionWorkbenchController?.originFor?.(sessionId);
1947
+ return !scope?.originWorkspaceId || task.originWorkspaceId === scope.originWorkspaceId;
1948
+ }
1949
+ function workflowOriginLabel(task) {
1950
+ if (!task?.originSessionId) return '历史版本未保存来源';
1951
+ return `工作区:${task.originWorkspaceName || task.originWorkspaceId || '未取得来源工作区'} · 会话:${task.originSessionName || task.originSessionId}`;
1952
+ }
1499
1953
  function cacheWorkflowTask(actions, sessionId, task) {
1500
1954
  if (!task) return null;
1955
+ if (!taskBelongsToSession(task, sessionId)) throw new Error('此任务不属于当前会话;请在任务历史中只读查看或打开来源会话。');
1956
+ const previous = workflowTaskBySession.get(String(sessionId));
1957
+ if (previous?.id === task.id && previous.revision > task.revision) return previous;
1501
1958
  if (sessionId) workflowTaskBySession.set(String(sessionId), task);
1502
1959
  bindTask(sessionId, task.id);
1503
1960
  actions.setWorkflowTask(task);
@@ -1510,10 +1967,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1510
1967
  }
1511
1968
 
1512
1969
  async function createWorkflowTask(actions, sessionId, draft = {}) {
1970
+ if (!sessionId) throw new Error('请先打开数据清洗会话。');
1513
1971
  const key = String(sessionId || 'unassigned');
1514
1972
  let pending = workflowTaskCreationBySession.get(key);
1515
1973
  if (!pending) {
1516
1974
  pending = requestJson('/data-cleaning/api/workflow/tasks', 'POST', {
1975
+ ...sessionWorkbenchController?.originFor?.(sessionId),
1976
+ originSessionId: sessionId,
1517
1977
  title: draft.title || '企业数据清洗补全任务',
1518
1978
  objectives: draft.objectives || DEFAULT_CLEANING_KEYS,
1519
1979
  fieldSelection: draft.fieldSelection || DEFAULT_ENRICHMENT_KEYS,
@@ -1539,9 +1999,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1539
1999
  const saved = readTaskBindings()[key];
1540
2000
  if (ownedSessionId(key) && /^dcw-[a-zA-Z0-9-]+$/.test(saved || '')) {
1541
2001
  const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(saved)}`);
1542
- const task = cacheWorkflowTask(actions, sessionId, response.task);
1543
- if (restoreStage) actions.setStep(task.stage || 'upload');
1544
- return task;
2002
+ if (taskBelongsToSession(response.task, sessionId)) {
2003
+ const task = cacheWorkflowTask(actions, sessionId, response.task);
2004
+ if (restoreStage) actions.setStep(task.stage || 'upload');
2005
+ return task;
2006
+ }
2007
+ bindTask(sessionId, null);
1545
2008
  }
1546
2009
  return createWorkflowTask(actions, sessionId, draft);
1547
2010
  }
@@ -1555,6 +2018,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1555
2018
  async function ensureEditableWorkflowTask(actions, sessionId, draft = {}, preferredTask) {
1556
2019
  const key = String(sessionId || 'unassigned');
1557
2020
  const current = workflowTaskBySession.get(key) ?? preferredTask ?? null;
2021
+ if (current && !taskBelongsToSession(current, sessionId)) throw new Error('不能将历史任务绑定到当前会话。请新建清洗任务。');
1558
2022
  if (!current) return createWorkflowTask(actions, sessionId, draft);
1559
2023
  if (EDITABLE_WORKFLOW_STATES.has(current.state)) return cacheWorkflowTask(actions, sessionId, current);
1560
2024
  workflowTaskBySession.delete(key);
@@ -1578,8 +2042,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1578
2042
  async function updateWorkflowTask(actions, sessionId, task, input) {
1579
2043
  const latest = workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? task;
1580
2044
  if (!latest) return ensureWorkflowTask(actions, sessionId, input);
2045
+ if (!taskBelongsToSession(latest, sessionId)) throw new Error('此任务不属于当前会话。');
1581
2046
  const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(latest.id)}`, 'PATCH', {
1582
2047
  ...input,
2048
+ originSessionId: sessionId, originWorkspaceId: latest.originWorkspaceId,
1583
2049
  expectedRevision: latest.revision,
1584
2050
  });
1585
2051
  return cacheWorkflowTask(actions, sessionId, response.task);
@@ -1588,8 +2054,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1588
2054
  async function workflowAction(actions, sessionId, task, action, input = {}) {
1589
2055
  const latest = workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? task;
1590
2056
  if (!latest) throw new Error('请先创建数据清洗补全任务。');
2057
+ if (!taskBelongsToSession(latest, sessionId)) throw new Error('此任务不属于当前会话。');
1591
2058
  const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(latest.id)}/actions/${action}`, 'POST', {
1592
2059
  ...input,
2060
+ originSessionId: sessionId, originWorkspaceId: latest.originWorkspaceId,
1593
2061
  expectedRevision: latest.revision,
1594
2062
  });
1595
2063
  return cacheWorkflowTask(actions, sessionId, response.task);
@@ -1603,6 +2071,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1603
2071
  if (JSON.stringify([...(task.fieldSelection || [])].sort()) !== JSON.stringify([...(expected.enrichmentKeys || [])].sort())) throw new Error('图片任务字段配置尚未保存,请重试。');
1604
2072
  const bound = await requestJson(`/data-cleaning/api/images/commands/${encodeURIComponent(expected.imageCommandId)}`, 'PATCH', {
1605
2073
  taskId: task.id, expectedRevision: task.revision, executeOnSend: true,
2074
+ originSessionId: sessionId, originWorkspaceId: task.originWorkspaceId,
1606
2075
  });
1607
2076
  return { ...bound.command, prompt: buildTaskPrompt(expected) };
1608
2077
  }
@@ -1626,6 +2095,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1626
2095
  });
1627
2096
  const response = await requestJson('/data-cleaning/api/g5/commands', 'POST', {
1628
2097
  kind: 'enrich', workflowOwned: true, expectedRevision: task.revision, taskId: task.id,
2098
+ originSessionId: sessionId, originWorkspaceId: task.originWorkspaceId,
1629
2099
  rows: runtime.rows, headers: runtime.headers, fieldSelection: task.fieldSelection,
1630
2100
  });
1631
2101
  if (!response.command?.commandId || !response.command?.prompt) throw new Error('Host 未返回执行凭证;未发送任务。');
@@ -1634,6 +2104,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1634
2104
 
1635
2105
  async function refreshHostedTask(actions, sessionId, taskId, run, navigate = true) {
1636
2106
  const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(taskId)}`);
2107
+ if (!taskBelongsToSession(response.task, sessionId)) {
2108
+ if (readTaskBindings()[sessionId] === taskId) bindTask(sessionId, null);
2109
+ return null;
2110
+ }
1637
2111
  const selectedTask = workflowTaskBySession.get(String(sessionId));
1638
2112
  if (selectedTask && selectedTask.id !== taskId) return response.task;
1639
2113
  if (selectedTask && selectedTask.revision > response.task.revision) return selectedTask;
@@ -1691,6 +2165,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1691
2165
  workflowContract: null,
1692
2166
  workflowTask: null,
1693
2167
  workflowTasks: [],
2168
+ historyTask: null,
1694
2169
  taskTitle: '企业数据清洗补全任务',
1695
2170
  mappings: [],
1696
2171
  objectives: [...DEFAULT_CLEANING_KEYS],
@@ -1733,6 +2208,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1733
2208
  draft.workflowTasks = (Array.isArray(value) ? value : []).map(task =>
1734
2209
  (cached.get(task.id)?.revision ?? -1) > (task.revision ?? -1) ? cached.get(task.id) : task);
1735
2210
  },
2211
+ setHistoryTask: (draft, value) => { draft.historyTask = value; },
2212
+ refreshHistoryTask: (draft, value) => {
2213
+ if (value?.id === draft.historyTask?.id && value.revision >= draft.historyTask.revision) draft.historyTask = value;
2214
+ },
1736
2215
  setTaskTitle: (draft, value) => { draft.taskTitle = String(value ?? ''); },
1737
2216
  setMappings: (draft, value) => {
1738
2217
  const next = Array.isArray(value) ? value : [];
@@ -3261,11 +3740,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3261
3740
  } catch (error) { onError?.(error instanceof Error ? error.message : String(error)); }
3262
3741
  };
3263
3742
  const complete = expectedCount == null || expectedCount === total;
3264
- return h('section', { className: 'dcAgentSection', 'aria-label': title },
3265
- h('h3', null, title),
3266
- h('p', { className: 'dcAgentHint', role: 'status' }, complete
3267
- ? `共 ${total} 条,当前显示 ${total ? start + 1 : 0}–${Math.min(start + pageSize, total)} 条;共 ${columns.length} 列,可横向滚动。`
3268
- : `完整清单尚未就绪:预期 ${expectedCount} 条,当前载入 ${total} 条。请等待同步;刷新后若仍缺失,请重新上传源文件。`),
3743
+ return h('section', { className: 'dcAgentSection dcAgentDatasetPreview', 'aria-label': title },
3744
+ h('div', { className: 'dcAgentDatasetHeader' },
3745
+ h('h3', null, title),
3746
+ h('p', { className: 'dcAgentHint', role: 'status' }, complete
3747
+ ? `共 ${total} 条,当前显示 ${total ? start + 1 : 0}–${Math.min(start + pageSize, total)} 条;共 ${columns.length} 列,可横向滚动。`
3748
+ : `完整清单尚未就绪:预期 ${expectedCount} 条,当前载入 ${total} 条。请等待同步;刷新后若仍缺失,请重新上传源文件。`),
3749
+ ),
3269
3750
  h('div', { className: 'dcAgentPreviewTable', tabIndex: 0, role: 'region', 'aria-label': '表格预览,可横向和纵向滚动,表头固定', style: { overflow: 'auto', maxHeight: '420px' } },
3270
3751
  h('table', { className: 'dcAgentTable' },
3271
3752
  h('thead', null, h('tr', null, h('th', null, '序号'), columns.map((key) => h('th', { key }, resultFieldLabel(key))))),
@@ -3275,16 +3756,18 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3275
3756
  ))),
3276
3757
  ),
3277
3758
  ),
3278
- h('div', { className: 'dcAgentRow' },
3279
- h('button', { type: 'button', className: 'dcAgentButton', disabled: currentPage === 0, onClick: () => setPage(currentPage - 1) }, '上一页'),
3280
- h('span', null, `第 ${currentPage + 1} / ${Math.max(1, Math.ceil(total / pageSize))} 页`),
3281
- h('button', { type: 'button', className: 'dcAgentButton', disabled: start + pageSize >= total, onClick: () => setPage(currentPage + 1) }, '下一页'),
3282
- ),
3283
- h('div', { className: 'dcAgentRow' },
3284
- h('button', { type: 'button', className: 'dcAgentButton', disabled: !complete || !total, onClick: () => download('csv') }, `下载全部 ${total} 条 CSV`),
3285
- h('button', { type: 'button', className: 'dcAgentButton', disabled: !complete || !total, onClick: () => download('json') }, '下载原值 JSON'),
3759
+ h('div', { className: 'dcAgentDatasetFooter' },
3760
+ h('div', { className: 'dcAgentDatasetPagination', 'aria-label': '清单分页' },
3761
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: currentPage === 0, onClick: () => setPage(currentPage - 1) }, '上一页'),
3762
+ h('span', { className: 'dcAgentDatasetPage', 'aria-live': 'polite' }, `第 ${currentPage + 1} / ${Math.max(1, Math.ceil(total / pageSize))} 页`),
3763
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: start + pageSize >= total, onClick: () => setPage(currentPage + 1) }, '下一页'),
3764
+ ),
3765
+ h('div', { className: 'dcAgentDatasetDownloads', 'aria-label': '清单导出' },
3766
+ h('button', { type: 'button', className: 'dcAgentButton', 'aria-label': `下载全部 ${total} 条 CSV`, disabled: !complete || !total, onClick: () => download('csv') }, h(IconDownloadOutline16), h('span', null, `下载全部 ${total} 条 CSV`)),
3767
+ h('button', { type: 'button', className: 'dcAgentButton', 'aria-label': '下载原值 JSON', disabled: !complete || !total, onClick: () => download('json') }, h(IconDownloadOutline16), h('span', null, '下载原值 JSON')),
3768
+ ),
3769
+ h('p', { className: 'dcAgentHint dcAgentDatasetFooterNote' }, '下载包含全部行,不受分页影响。CSV 对公式及长编号作文本保护;JSON 保留原值。仅导出当前清单,不触发企查查调用。'),
3286
3770
  ),
3287
- h('p', { className: 'dcAgentHint' }, '下载包含全部行,不受分页影响。CSV 对公式及长编号作文本保护;JSON 保留原值。仅导出当前清单,不触发企查查调用。'),
3288
3771
  );
3289
3772
  }
3290
3773
 
@@ -3302,6 +3785,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3302
3785
  const workflowContract = useStore((state) => state.workflowContract);
3303
3786
  const workflowTask = useStore((state) => state.workflowTask);
3304
3787
  const workflowTasks = useStore((state) => state.workflowTasks);
3788
+ const historyTask = useStore((state) => state.historyTask);
3305
3789
  const taskTitle = useStore((state) => state.taskTitle);
3306
3790
  const mappings = useStore((state) => state.mappings);
3307
3791
  const objectives = useStore((state) => state.objectives);
@@ -3319,6 +3803,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3319
3803
  const paidConfirmed = useStore((state) => state.paidConfirmed);
3320
3804
  const activeSessionId = useStore((state) => state.activeSessionId);
3321
3805
  const [fieldQuery, setFieldQuery] = react.useState('');
3806
+ const [intakeEditorOpen, setIntakeEditorOpen] = react.useState(() => dataset == null);
3807
+
3808
+ react.useEffect(() => {
3809
+ setIntakeEditorOpen(dataset == null);
3810
+ }, [dataset]);
3811
+
3812
+ react.useEffect(() => {
3813
+ if (!open || !visible) return;
3814
+ const owner = [...(document.querySelectorAll?.('.dcAgentWorkbenchContent') || [])]
3815
+ .find((node) => node.dataset.sessionId === String(activeSessionId));
3816
+ const body = owner?.querySelector?.('.dcAgentWbBody');
3817
+ if (body) body.scrollTop = 0;
3818
+ }, [step, activeSessionId, open, visible]);
3322
3819
 
3323
3820
  react.useEffect(() => {
3324
3821
  if (!open || !visible) return undefined;
@@ -3349,6 +3846,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3349
3846
  try {
3350
3847
  const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null, false);
3351
3848
  if (disposed) return;
3849
+ if (!task) return;
3352
3850
  if (['matching', 'enriching'].includes(task.state)) timer = setTimeout(poll, 2000);
3353
3851
  else if (step === 'match' || step === 'enrich') actions.setStep(task.stage || step);
3354
3852
  } catch (error) { if (!disposed) actions.setError(error.message); }
@@ -3367,6 +3865,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3367
3865
  const stagedSource = runtimeFor(`session:${activeSessionId || 'unassigned'}`, false);
3368
3866
  const sourceRows = stagedSource?.sourceRows ?? runtime.sourceRows ?? runtime.rows;
3369
3867
  const intake = intakeState(dataset, sourceRows, input, runtime.source || stagedSource?.source || cachedTask?.source);
3868
+ const showIntakeEditor = !dataset || intakeEditorOpen;
3370
3869
  const lastCsv = runtime.lastCsv;
3371
3870
  const fieldByPattern = (pattern) => runtime.headers.find((field) => pattern.test(field)) ?? null;
3372
3871
  const phoneField = fieldByPattern(/^(phone|mobile|tel|telephone|联系电话|手机号码|手机号)$/i);
@@ -3583,8 +4082,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3583
4082
  throw new Error('当前会话无法提交数据清洗补全企查查任务');
3584
4083
  }
3585
4084
  const latest = workflowTaskBySession.get(String(activeSessionId)) ?? cachedTask;
4085
+ if (!taskBelongsToSession(latest, activeSessionId)) throw new Error('此任务不属于当前会话。');
3586
4086
  const prepared = await api('/data-cleaning/api/g5/commands', {
3587
4087
  ...payload, workflowOwned: true, expectedRevision: latest.revision,
4088
+ originSessionId: activeSessionId, originWorkspaceId: latest.originWorkspaceId,
3588
4089
  ...(payload.kind === 'enrich' ? {} : { confirmPaidCalls: true }),
3589
4090
  });
3590
4091
  const command = prepared?.command;
@@ -3737,28 +4238,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3737
4238
  };
3738
4239
 
3739
4240
  const resumeWorkflowTask = async (task) => {
4241
+ // History selection has its own state; never hydrate the current task runtime.
4242
+ actions.setHistoryTask(task);
3740
4243
  try {
3741
- workflowTaskBySession.set(String(activeSessionId), task);
3742
- task = await refreshHostedTask(actions, activeSessionId, task.id);
3743
- if (task.qccRunId) {
3744
- try {
3745
- const response = await api(`/data-cleaning/api/g5/run/${encodeURIComponent(task.qccRunId)}`);
3746
- actions.setQccRun(response);
3747
- } catch { /* Expired run data does not prevent persisted artifact downloads. */ }
3748
- }
4244
+ const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(task.id)}`);
4245
+ actions.refreshHistoryTask(response.task);
3749
4246
  } catch (error) { actions.setError(error.message); return; }
3750
- workflowTaskBySession.set(String(activeSessionId || 'unassigned'), task);
3751
- actions.setWorkflowTask(task);
3752
- actions.setTaskTitle(task.title);
3753
- actions.setMappings(task.mappings || []);
3754
- actions.setObjectives(task.objectives || []);
3755
- actions.setFieldSelection(task.fieldSelection || []);
3756
- for (const [key, value] of Object.entries(task.matchRules || {})) actions.setMatchRule(key, value);
3757
- actions.setStep(task.stage || 'upload');
3758
- if (!runtimeFor(task.id, false)?.rows?.length && task.source?.rowCount && !(task.artifacts || []).length) {
3759
- actions.setDataset(null);
3760
- actions.setError('已恢复任务元数据。出于隐私保护,原始企业名单未持久化;继续处理前请重新上传源文件。');
3761
- }
3762
4247
  };
3763
4248
 
3764
4249
  const startNewWorkflow = () => {
@@ -3788,6 +4273,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3788
4273
  const rows = exportRows();
3789
4274
  if (!rows?.length) throw new Error('当前页面没有可导出的明细。若任务来自历史记录,请重新上传源文件并继续处理。');
3790
4275
  const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(latest.id)}/artifacts`, 'POST', {
4276
+ originSessionId: activeSessionId, originWorkspaceId: latest.originWorkspaceId,
3791
4277
  expectedRevision: latest.revision,
3792
4278
  headers: runtime.headers,
3793
4279
  rows,
@@ -3887,7 +4373,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3887
4373
  let pane;
3888
4374
  if (step === 'history') {
3889
4375
  pane = h('div', { className: 'dcAgentPane' },
3890
- h('p', { className: 'dcAgentHint' }, '查看当前 Host 的数据清洗补全任务。Host 只保存任务元数据和摘要,不保存原始企业名单。'),
4376
+ h('p', { className: 'dcAgentHint' }, '当前 Profile 的任务历史,仅只读查看;不会改变当前会话的任务、名单、规则或执行范围。'),
3891
4377
  h('div', { className: 'dcAgentRow' },
3892
4378
  h('button', {
3893
4379
  type: 'button',
@@ -3902,9 +4388,29 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3902
4388
  onClick: startNewWorkflow,
3903
4389
  }, '新建清洗任务'),
3904
4390
  ),
4391
+ historyTask ? h('section', { className: 'dcAgentSection', 'aria-label': '历史任务只读详情' },
4392
+ h('h3', null, workflowDisplayName(historyTask)),
4393
+ h('p', { className: 'dcAgentHint' }, workflowOriginLabel(historyTask)),
4394
+ h('p', null, `只读 · ${WORKFLOW_STATE_LABELS[historyTask.state] || '未知'} · ${historyTask.source?.rowCount ?? 0} 行`),
4395
+ h('p', { className: 'dcAgentHint' }, `任务编号:${historyTask.id}`),
4396
+ sessionWorkbenchController?.canOpenOrigin?.(historyTask)
4397
+ ? h('button', { type: 'button', className: 'dcAgentButton', onClick: () => {
4398
+ try { sessionWorkbenchController.openOrigin(historyTask); }
4399
+ catch (error) { actions.setError(error.message); }
4400
+ } }, '打开来源会话')
4401
+ : h('p', { className: 'dcAgentHint' }, '来源会话未知、已不可用或宿主不支持打开;保留只读查看和已有文件下载。'),
4402
+ (historyTask.artifacts || []).filter(a => a.format === 'xlsx').map(artifact =>
4403
+ h('div', { key: artifact.id, className: 'dcAgentRow' },
4404
+ artifact.kind !== 'report' ? h('a', { className: 'dcAgentButton', href: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(historyTask.id)}/artifacts/${encodeURIComponent(artifact.id)}?preview=1`, target: '_blank', rel: 'noopener' }, '预览 / 打开') : null,
4405
+ h('a', { className: artifact.kind === 'report' ? 'dcAgentHint' : 'dcAgentButton',
4406
+ href: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(historyTask.id)}/artifacts/${encodeURIComponent(artifact.id)}`,
4407
+ download: artifact.fileName }, artifact.kind === 'report' ? '下载任务结果报告' : `下载 ${artifact.fileName}`))),
4408
+ !historyTask.artifacts?.length ? h('p', { className: 'dcAgentHint' }, '此任务暂无已保存制品;历史查看不会重新调用企查查。') : null,
4409
+ ) : null,
3905
4410
  workflowTasks.length ? h('div', { className: 'dcAgentPane', 'aria-label': '任务历史列表' },
3906
4411
  workflowTasks.map((task) => h('button', { key: task.id, type: 'button', className: 'dcAgentHistoryTask', onClick: () => resumeWorkflowTask(task) },
3907
4412
  h('h3', null, workflowDisplayName(task)),
4413
+ h('p', { className: 'dcAgentHint' }, workflowOriginLabel(task)),
3908
4414
  h('div', { className: 'dcAgentRow' },
3909
4415
  h('span', { className: 'dcAgentJobsPill', 'data-state': task.state ?? 'idle' }, WORKFLOW_STATE_LABELS[task.state] ?? task.state ?? '未知'),
3910
4416
  h('span', { className: 'dcAgentHint' }, `${task.source?.rowCount ?? 0} 行 · ${task.updatedAt ? new Date(task.updatedAt).toLocaleString() : ''}`),
@@ -3922,16 +4428,31 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3922
4428
  h('span', null, '任务名称'),
3923
4429
  h('input', { className: 'dcAgentField', value: taskTitle, maxLength: 120, onChange: (event) => actions.setTaskTitle(event.target.value) }),
3924
4430
  ),
3925
- h('section', { className: 'dcAgentSection' },
3926
- h('h3', null, '字段映射'),
3927
- h('p', { className: 'dcAgentHint' }, '至少映射企业名称、统一社会信用代码或注册号其中一项。未映射列会原样保留,但不参与匹配。'),
3928
- h('p', { className: 'dcAgentHint' }, `已映射 ${mappings.length} / ${dataset?.headers?.length || 0} 列。绿色=通过;黄色=待确认;红色=未匹配。确认映射自动选入补全范围,不调用模型或企查查。多个原列可确认使用同一补全字段,主体匹配标识只选一列。`),
3929
- h('button', { type: 'button', className: 'dcAgentButton', onClick: () => actions.setMappings(mergeRecommendedMappings(dataset?.headers || [], mappings, targetFields)) }, '补充自动映射(保留已有选择)'),
3930
- mappingHints.map((recommendation, index) => h(MappingPicker, {
3931
- key: `${index}:${recommendation.sourceField}`, sourceField: recommendation.sourceField,
3932
- mappings, groups: [...uiFieldGroups, ['input-only', '输入校验字段', inputOnlyMappingFields]],
3933
- recommendation, onChange: actions.setMapping,
3934
- })),
4431
+ h('section', { className: 'dcAgentSection dcAgentMappingSection', 'aria-label': '字段映射' },
4432
+ h('div', { className: 'dcAgentMappingHeader' },
4433
+ h('div', null,
4434
+ h('h3', null, '字段映射'),
4435
+ h('p', { className: 'dcAgentHint' }, '至少映射企业名称、统一社会信用代码或注册号其中一项。未映射列会原样保留,但不参与匹配。'),
4436
+ ),
4437
+ h('div', { className: 'dcAgentMappingProgress', 'aria-label': `已映射 ${mappings.length} / ${dataset?.headers?.length || 0} 列` },
4438
+ h('b', null, `${mappings.length} / ${dataset?.headers?.length || 0}`), h('span', null, '列已映射')),
4439
+ ),
4440
+ h('div', { className: 'dcAgentMappingToolbar' },
4441
+ h('div', { className: 'dcAgentMappingLegend', 'aria-label': '映射状态说明' },
4442
+ h('span', { className: 'dcAgentMappingLegendItem', 'data-status': 'confirmed' }, '通过'),
4443
+ h('span', { className: 'dcAgentMappingLegendItem', 'data-status': 'review' }, '待确认'),
4444
+ h('span', { className: 'dcAgentMappingLegendItem', 'data-status': 'unmatched' }, '未匹配')),
4445
+ h('button', { type: 'button', className: 'dcAgentButton dcAgentMappingAuto', onClick: () => actions.setMappings(mergeRecommendedMappings(dataset?.headers || [], mappings, targetFields)) }, '补充自动映射(保留已有选择)'),
4446
+ ),
4447
+ h('p', { className: 'dcAgentHint dcAgentMappingNote' }, '确认映射自动选入补全范围,不调用模型或企查查。多个原列可确认使用同一补全字段,主体匹配标识只选一列。'),
4448
+ h('div', { className: 'dcAgentMappingList' },
4449
+ h('div', { className: 'dcAgentMappingListHead', 'aria-hidden': 'true' }, h('span', null, '原始列'), h('span', null, '目标字段')),
4450
+ mappingHints.map((recommendation, index) => h(MappingPicker, {
4451
+ key: `${index}:${recommendation.sourceField}`, sourceField: recommendation.sourceField,
4452
+ mappings, groups: [...uiFieldGroups, ['input-only', '输入校验字段', inputOnlyMappingFields]],
4453
+ recommendation, onChange: actions.setMapping,
4454
+ })),
4455
+ ),
3935
4456
  ),
3936
4457
  h('section', { className: 'dcAgentSection' },
3937
4458
  h('h3', null, '任务目标'),
@@ -3985,56 +4506,83 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3985
4506
  ),
3986
4507
  );
3987
4508
  } else if (step === 'profile') {
3988
- pane = h('div', { className: 'dcAgentPane' },
3989
- h('p', { className: 'dcAgentHint' }, '基于已解析数据生成本地质量画像(缺失率、去重值、金额分布)。本步骤不发起企查查调用。'),
3990
- profile ? h('div', null,
3991
- h('div', { className: 'dcAgentGrid' },
3992
- stat('行数', profile.rowCount ?? '—'),
3993
- stat('列数', profile.columnCount ?? '—'),
3994
- profile.amountStats ? stat('金额 最小', profile.amountStats.min) : null,
3995
- profile.amountStats ? stat('金额 最大', profile.amountStats.max) : null,
3996
- profile.amountStats ? stat('金额 总和', profile.amountStats.sum) : null,
3997
- profile.amountStats ? stat('金额 均值', typeof profile.amountStats.mean === 'number' ? profile.amountStats.mean.toFixed(2) : profile.amountStats.mean) : null,
4509
+ pane = h('div', { className: 'dcAgentPane dcAgentProfilePane' },
4510
+ h('section', { className: 'dcAgentSection dcAgentProfileSurface', 'aria-label': '质量体检报告' },
4511
+ h('header', { className: 'dcAgentProfileHeader' },
4512
+ h('h3', null, '质量体检报告'),
4513
+ h('p', { className: 'dcAgentHint' }, '基于已解析数据生成本地质量画像(缺失率、去重值、金额分布)。本步骤不发起企查查调用。'),
3998
4514
  ),
3999
- Array.isArray(profile.columns) && profile.columns.length ? h('table', { className: 'dcAgentTable' },
4000
- h('thead', null, h('tr', null,
4001
- h('th', null, '字段'),
4002
- h('th', null, '非空'),
4003
- h('th', null, '缺失'),
4004
- h('th', null, '去重值'),
4005
- )),
4006
- h('tbody', null, profile.columns.map((col) => h('tr', { key: col.name },
4007
- h('td', null, col.name),
4008
- h('td', { className: 'num' }, String(col.present)),
4009
- h('td', { className: 'num' }, String(col.missing)),
4010
- h('td', { className: 'num' }, String(col.distinct)),
4011
- ))),
4012
- ) : null,
4013
- h('div', { className: 'dcAgentRow' },
4014
- h('button', {
4015
- type: 'button',
4016
- className: 'dcAgentButton is-primary',
4017
- disabled: busy,
4018
- onClick: () => actions.setStep(requiresQcc ? 'match' : 'enrich'),
4019
- }, requiresQcc ? '下一步:匹配核验' : '下一步:本地清洗补全'),
4515
+ profile ? h('div', { className: 'dcAgentProfileReport' },
4516
+ h('div', { className: 'dcAgentGrid dcAgentProfileMetrics', 'aria-label': '体检摘要' },
4517
+ stat('行数', profile.rowCount ?? '—'),
4518
+ stat('列数', profile.columnCount ?? '—'),
4519
+ profile.amountStats ? stat('金额 最小', profile.amountStats.min) : null,
4520
+ profile.amountStats ? stat('金额 最大', profile.amountStats.max) : null,
4521
+ profile.amountStats ? stat('金额 总和', profile.amountStats.sum) : null,
4522
+ profile.amountStats ? stat('金额 均值', typeof profile.amountStats.mean === 'number' ? profile.amountStats.mean.toFixed(2) : profile.amountStats.mean) : null,
4523
+ ),
4524
+ Array.isArray(profile.columns) && profile.columns.length ? h('section', { className: 'dcAgentProfileTableSection', 'aria-label': '字段质量明细' },
4525
+ h('div', { className: 'dcAgentProfileTableHead' },
4526
+ h('h4', null, '字段质量明细'),
4527
+ h('span', { className: 'dcAgentHint' }, `共 ${profile.columns.length} 列`),
4528
+ ),
4529
+ h('div', { className: 'dcAgentProfileTableWrap', tabIndex: 0 },
4530
+ h('table', { className: 'dcAgentTable' },
4531
+ h('thead', null, h('tr', null,
4532
+ h('th', null, '字段'),
4533
+ h('th', null, '非空'),
4534
+ h('th', null, '缺失'),
4535
+ h('th', null, '去重值'),
4536
+ )),
4537
+ h('tbody', null, profile.columns.map((col) => h('tr', { key: col.name },
4538
+ h('td', null, col.name),
4539
+ h('td', { className: 'num' }, String(col.present)),
4540
+ h('td', { className: 'num' }, String(col.missing)),
4541
+ h('td', { className: 'num' }, String(col.distinct)),
4542
+ ))),
4543
+ ),
4544
+ ),
4545
+ ) : null,
4546
+ h('div', { className: 'dcAgentProfileActions' },
4547
+ h('button', {
4548
+ type: 'button',
4549
+ className: 'dcAgentButton is-primary',
4550
+ disabled: busy,
4551
+ onClick: () => actions.setStep(requiresQcc ? 'match' : 'enrich'),
4552
+ }, requiresQcc ? '下一步:匹配核验' : '下一步:本地清洗补全'),
4553
+ ),
4554
+ ) : h('div', { className: 'dcAgentProfileEmpty' },
4555
+ h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData, 'aria-label': '生成体检报告', onClick: runProfile }, busy ? '体检中…' : '生成体检报告'),
4020
4556
  ),
4021
- ) : h('div', { className: 'dcAgentRow' },
4022
- h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData, 'aria-label': '生成体检报告', onClick: runProfile }, busy ? '体检中…' : '生成体检报告'),
4023
4557
  ),
4024
4558
  );
4025
4559
  } else if (step === 'match') {
4026
- pane = h('div', { className: 'dcAgentPane' },
4027
- h('p', { className: 'dcAgentHint' }, '通过当前用户连接的企查查 MCP 定位企业主体,并按已选维度补全一企一行字段。多候选始终由人工选择;界面不生成虚构置信度。'),
4028
- h('section', { className: 'dcAgentSection' },
4029
- h('h3', null, '匹配依据'),
4030
- h('div', { className: 'dcAgentChips' }, mappings.filter((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField)).map((mapping) => h('span', { key: mapping.sourceField, className: 'dcAgentChip' }, `${mapping.sourceField} → ${resultFieldLabel(mapping.targetField)}`))),
4031
- h('p', { className: 'dcAgentHint' }, '统一社会信用代码等强标识优先;名称匹配不明确时进入人工核验队列。'),
4032
- ),
4033
- h('section', { className: 'dcAgentSection' },
4034
- h('h3', null, '准备任务说明'),
4035
- h('p', { className: 'dcAgentHint' }, `已导入 ${dataset?.rowCount ?? runtime.rows.length} 条记录,已选择 ${fieldSelection.length} 个补全字段。Host 自动检查所需工具并计算去重主体与调用上界。`),
4036
- h('p', { className: 'dcAgentHint' }, '回填后可修改;点击发送才开始查询,使用你已连接的企查查 MCP 账号及额度。'),
4037
- h('div', { className: 'dcAgentRow' },
4560
+ const identityMappings = mappings.filter((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
4561
+ pane = h('div', { className: 'dcAgentPane dcAgentMatchPane' },
4562
+ h('section', { className: 'dcAgentSection dcAgentMatchSurface', 'aria-label': '主体匹配执行准备' },
4563
+ h('header', { className: 'dcAgentMatchHeader' },
4564
+ h('h3', null, '主体匹配'),
4565
+ h('p', { className: 'dcAgentHint' }, '通过当前用户连接的企查查 MCP 定位企业主体,并按已选维度补全一企一行字段。多候选始终由人工选择;界面不生成虚构置信度。'),
4566
+ ),
4567
+ h('div', { className: 'dcAgentMatchContent' },
4568
+ h('section', { className: 'dcAgentMatchBlock', 'aria-label': '匹配依据' },
4569
+ h('div', { className: 'dcAgentMatchBlockHead' },
4570
+ h('h4', null, '匹配依据'),
4571
+ h('span', { className: 'dcAgentMatchScope' }, h('b', null, identityMappings.length), '项'),
4572
+ ),
4573
+ h('div', { className: 'dcAgentChips' }, identityMappings.map((mapping) => h('span', { key: mapping.sourceField, className: 'dcAgentChip' }, `${mapping.sourceField} → ${resultFieldLabel(mapping.targetField)}`))),
4574
+ h('p', { className: 'dcAgentHint' }, '统一社会信用代码等强标识优先;名称匹配不明确时进入人工核验队列。'),
4575
+ ),
4576
+ h('section', { className: 'dcAgentMatchBlock', 'aria-label': '准备任务说明' },
4577
+ h('div', { className: 'dcAgentMatchBlockHead' },
4578
+ h('h4', null, '准备任务说明'),
4579
+ h('span', { className: 'dcAgentMatchScope' }, h('b', null, dataset?.rowCount ?? runtime.rows.length), '条记录'),
4580
+ ),
4581
+ h('p', { className: 'dcAgentHint' }, `已选择 ${fieldSelection.length} 个补全字段。Host 自动检查所需工具并计算去重主体与调用上界。`),
4582
+ h('p', { className: 'dcAgentHint' }, '回填后可修改;点击发送才开始查询,使用你已连接的企查查 MCP 账号及额度。'),
4583
+ ),
4584
+ ),
4585
+ h('div', { className: 'dcAgentMatchActions' },
4038
4586
  h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData || !cachedTask?.id, onClick: runQcc }, busy ? '检查并生成中…' : '生成可编辑任务说明'),
4039
4587
  ),
4040
4588
  ),
@@ -4138,44 +4686,59 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4138
4686
  );
4139
4687
  } else {
4140
4688
  pane = h('div', { className: 'dcAgentPane' },
4141
- h('p', { className: 'dcAgentHint' }, '上传 CSV / XLSX / XLS / JSON,或粘贴数据。也可选择图片,通过企查查智能文档解析提取名单后继续清洗补全。'),
4142
- dataset ? h('section', { className: 'dcAgentSection', 'aria-label': '当前导入数据', role: 'status' },
4143
- h('h3', null, intake.available ? `已解析 ${intake.count} 条数据` : '已保存导入摘要'),
4144
- h('p', { className: 'dcAgentHint', style: { overflowWrap: 'anywhere' } }, `当前来源:${intake.fileName}`),
4689
+ dataset ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
4690
+ h('div', { className: 'dcAgentIntakeSummaryHead' },
4691
+ h('div', null,
4692
+ h('h3', null, intake.available ? `已解析 ${intake.count} 条数据` : '已保存导入摘要'),
4693
+ h('p', { className: 'dcAgentHint', style: { overflowWrap: 'anywhere' } }, `当前来源:${intake.fileName}`),
4694
+ ),
4695
+ ),
4145
4696
  h('p', { className: 'dcAgentHint' }, intake.available
4146
- ? '文件选择后已自动解析,无需再次点击解析。核对下方名单后可直接进入规则与体检;重新选择文件会替换当前导入数据。'
4697
+ ? '数据已导入。请核对下方完整名单;需要替换来源时点击“重新导入”。'
4147
4698
  : '当前页面没有完整原始数据。请重新导入原文件;历史下载制品仍可从任务历史获取。'),
4148
4699
  ) : null,
4149
- h('div', { className: 'dcAgentRow' },
4150
- h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
4151
- onClick: (event) => event.currentTarget.nextElementSibling?.click() },
4152
- dataset ? '重新选择文件(自动解析)' : '选择文件(自动解析)'),
4153
- h('input', {
4154
- type: 'file',
4155
- accept: '.csv,.json,.xlsx,.xls,image/png,image/jpeg,image/webp',
4156
- className: 'dcAgentField',
4157
- style: { display: 'none' },
4158
- disabled: busy,
4159
- 'aria-label': '选择数据文件',
4160
- onChange: handleFile,
4161
- })),
4162
- h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
4163
- onClick: () => openImageIntake() }, '图片识别(粘贴 / 选择)'),
4164
- h('textarea', {
4165
- className: 'dcAgentTextarea',
4166
- placeholder: '粘贴 CSV 文本,或 JSON 数组(例如 [{"name":"某公司","amount":"100"}])…',
4167
- 'aria-label': '粘贴数据',
4168
- value: input,
4169
- disabled: busy,
4170
- onPaste: (event) => {
4171
- const files = imageFilesFromTransfer(event.clipboardData);
4172
- if (files.length && openImageIntake(files[0])) {
4173
- event.preventDefault();
4174
- event.stopPropagation?.();
4175
- }
4176
- },
4177
- onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); },
4178
- }),
4700
+ showIntakeEditor ? h('section', { id: 'dc-agent-intake-editor', className: 'dcAgentSection dcAgentIntakeEditor', 'aria-label': dataset ? '重新导入数据' : '导入数据' },
4701
+ h('header', { className: 'dcAgentIntakeEditorHeader' },
4702
+ h('h3', null, dataset ? '重新导入数据' : '导入数据'),
4703
+ h('p', { className: 'dcAgentHint' }, '选择表格或 JSON 文件、识别图片,或者粘贴 CSV / JSON 数据。成功解析后此区域会自动收起。'),
4704
+ ),
4705
+ h('div', { className: 'dcAgentIntakeSourceActions' },
4706
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
4707
+ onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择文件(自动解析)'),
4708
+ h('input', {
4709
+ type: 'file',
4710
+ accept: '.csv,.json,.xlsx,.xls,image/png,image/jpeg,image/webp',
4711
+ className: 'dcAgentField',
4712
+ style: { display: 'none' },
4713
+ disabled: busy,
4714
+ 'aria-label': '选择数据文件',
4715
+ onChange: handleFile,
4716
+ }),
4717
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
4718
+ onClick: () => openImageIntake() }, '图片识别(粘贴 / 选择)'),
4719
+ ),
4720
+ h('div', { className: 'dcAgentIntakePaste' },
4721
+ h('textarea', {
4722
+ className: 'dcAgentTextarea',
4723
+ placeholder: '粘贴 CSV 文本,或 JSON 数组(例如 [{"name":"某公司","amount":"100"}])…',
4724
+ 'aria-label': '粘贴数据',
4725
+ value: input,
4726
+ disabled: busy,
4727
+ onPaste: (event) => {
4728
+ const files = imageFilesFromTransfer(event.clipboardData);
4729
+ if (files.length && openImageIntake(files[0])) {
4730
+ event.preventDefault();
4731
+ event.stopPropagation?.();
4732
+ }
4733
+ },
4734
+ onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); },
4735
+ }),
4736
+ ),
4737
+ h('div', { className: 'dcAgentIntakeEditorActions' },
4738
+ h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !intake.pendingText, 'aria-label': '解析数据', onClick: handleParse }, busy ? '解析中…' : '解析粘贴内容'),
4739
+ intake.pendingText && hasData ? h('p', { className: 'dcAgentHint', role: 'status' }, '有尚未解析的新粘贴内容,请先解析或清空后再继续。') : null,
4740
+ ),
4741
+ ) : null,
4179
4742
  dataset ? h('div', { className: 'dcAgentChips' },
4180
4743
  h('span', { className: 'dcAgentChip' }, `格式 ${dataset.fmt}`),
4181
4744
  h('span', { className: 'dcAgentChip' }, `${dataset.rowCount} 行`),
@@ -4189,10 +4752,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4189
4752
  onError: actions.setError,
4190
4753
  }) : null,
4191
4754
  hasData ? h('p', { className: 'dcAgentHint' }, '请核对完整名单。识别清单不代表已完成企查查匹配。核对后进入「规则与体检」,确认规则生成质量体检;再进入「主体匹配」直接生成可编辑任务说明。Host 自动检查工具和调用范围,回填中央对话框后点击发送才开始查询。') : null,
4192
- h('div', { className: 'dcAgentRow' },
4193
- h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !intake.pendingText, 'aria-label': '解析数据', onClick: handleParse }, busy ? '解析中…' : '解析粘贴内容'),
4194
- intake.pendingText && hasData ? h('p', { className: 'dcAgentHint', role: 'status' }, '有尚未解析的新粘贴内容,请先解析或清空后再继续。') : null,
4195
- hasData ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !intake.available || intake.pendingText, onClick: () => { actions.setError(null); actions.setStep('rules'); } }, '已核对清单,下一步:字段映射与规则') : null,
4755
+ h('div', { className: 'dcAgentUploadActions' },
4756
+ hasData && !intakeEditorOpen ? h('button', {
4757
+ type: 'button', className: 'dcAgentButton', disabled: busy,
4758
+ 'aria-expanded': false, 'aria-controls': 'dc-agent-intake-editor',
4759
+ onClick: () => { actions.setError(null); setIntakeEditorOpen(true); },
4760
+ }, '重新导入') : null,
4761
+ hasData ? h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !intake.available || intake.pendingText, onClick: () => { actions.setError(null); actions.setStep('rules'); } }, '已核对清单,下一步:字段映射与规则') : null,
4196
4762
  ),
4197
4763
  );
4198
4764
  }
@@ -4242,7 +4808,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4242
4808
  h('p', { className: 'dcAgentHint' }, '导入与核验 → 规则与体检 → 主体匹配 → 字段补全 → 结果下载。菜单只切换视图,不启动调用;按页面主按钮逐步确认。'),
4243
4809
  h('p', { className: 'dcAgentHint' }, '支持文本、Excel、JSON 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
4244
4810
  ) : null,
4245
- ['rules', 'profile'].includes(step) ? h('nav', { className: 'dcAgentRow', 'aria-label': '规则与体检视图' },
4811
+ ['rules', 'profile'].includes(step) ? h('nav', { className: 'dcAgentViewTabs', 'aria-label': '规则与体检视图' },
4246
4812
  h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () => actions.setStep('rules') }, '字段映射与规则'),
4247
4813
  h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () => actions.setStep('profile') }, '质量体检报告'),
4248
4814
  ) : null,
@@ -4443,6 +5009,27 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4443
5009
  : null;
4444
5010
  const disconnectFixture = typeof ctx.inject !== 'function' ? connectProvider(ctx.betterSidebar) : () => {};
4445
5011
  const controller = {
5012
+ originFor(sessionId) {
5013
+ const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
5014
+ const workspaces = typeof ctx.get === 'function' ? ctx.get('workspaces') : ctx.workspaces;
5015
+ const summary = sessions?.list?.getSnapshot?.()?.byId?.[sessionId];
5016
+ const workspace = workspaces?.list?.getSnapshot?.()?.items?.find(item => item.sessionIds?.includes(sessionId));
5017
+ return { originSessionId: sessionId, originWorkspaceId: workspace?.workspaceId || summary?.workspaceId || null,
5018
+ originSessionName: summary?.title || null, originWorkspaceName: workspace?.name || workspace?.title || null };
5019
+ },
5020
+ canOpenOrigin(task) {
5021
+ const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
5022
+ if (!task?.originSessionId || !ownedSessionId(task.originSessionId) || typeof sessions?.open !== 'function') return false;
5023
+ const summary = sessions.list?.getSnapshot?.()?.byId?.[task.originSessionId];
5024
+ const scope = controller.originFor(task.originSessionId);
5025
+ return Boolean(summary && (!task.originWorkspaceId || scope.originWorkspaceId === task.originWorkspaceId));
5026
+ },
5027
+ openOrigin(task) {
5028
+ if (!controller.canOpenOrigin(task)) throw new Error('来源会话不可用;请继续只读查看。');
5029
+ const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
5030
+ sessions.open(task.originSessionId);
5031
+ // Navigation only: no task binding, command preparation, or draft writes.
5032
+ },
4446
5033
  storeFor: instanceFor,
4447
5034
  actionsFor: sessionId => instanceFor(sessionId).actions,
4448
5035
  open(sessionId, step) {
@@ -4645,6 +5232,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4645
5232
  ensureWorkflowTask,
4646
5233
  ensureEditableWorkflowTask,
4647
5234
  queueWorkflowOperation,
5235
+ taskBelongsToSession,
5236
+ workflowOriginLabel,
5237
+ cacheWorkflowTask,
5238
+ refreshHostedTask,
4648
5239
  };
4649
5240
  return module.exports;
4650
5241
  },