@xcanwin/manyoyo 5.7.14 → 5.7.20

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.
@@ -14,6 +14,8 @@
14
14
  --line: #d9c7ad;
15
15
  --line-strong: #b59263;
16
16
  --line-soft: rgba(181, 146, 99, 0.26);
17
+ --tree-guide: rgba(181, 146, 99, 0.44);
18
+ --tree-guide-soft: rgba(181, 146, 99, 0.2);
17
19
 
18
20
  --text: #1f1a14;
19
21
  --muted: #6a5f52;
@@ -36,6 +38,7 @@
36
38
  --status-stopped-text: #9a5a09;
37
39
  --status-history-bg: #ece7df;
38
40
  --status-history-text: #645647;
41
+ --tree-history-text: #b7b0a7;
39
42
  --status-unknown-bg: #ece9ff;
40
43
  --status-unknown-text: #5a4bb0;
41
44
 
@@ -546,8 +549,8 @@ button.tree-toggle {
546
549
  padding: 10px 12px;
547
550
  display: flex;
548
551
  align-items: flex-start;
549
- justify-content: space-between;
550
- gap: 12px;
552
+ justify-content: flex-start;
553
+ gap: 0;
551
554
  }
552
555
 
553
556
  button.tree-toggle:hover {
@@ -560,320 +563,267 @@ button.tree-toggle:active {
560
563
  transform: none;
561
564
  }
562
565
 
563
- button.tree-toggle:focus-visible {
564
- outline: none;
565
- box-shadow: 0 0 0 3px rgba(196, 85, 31, 0.14);
566
- }
567
-
568
- .tree-toggle-main {
569
- min-width: 0;
566
+ .tree-node-block,
567
+ .tree-node-children {
570
568
  display: flex;
571
569
  flex-direction: column;
572
- gap: 4px;
570
+ gap: 8px;
573
571
  }
574
572
 
575
- .tree-toggle-meta {
576
- color: var(--muted);
577
- font-size: 11px;
578
- line-height: 1.45;
579
- word-break: break-word;
573
+ .tree-node-row {
574
+ display: grid;
575
+ grid-template-columns: auto minmax(0, 1fr);
576
+ align-items: stretch;
577
+ column-gap: 8px;
580
578
  }
581
579
 
582
- .tree-toggle-caret {
583
- color: #8c7257;
584
- font-size: 18px;
585
- line-height: 1;
586
- flex-shrink: 0;
587
- transition: transform 140ms ease;
580
+ .tree-node-row.tree-node-row-container {
581
+ position: relative;
582
+ grid-template-columns: auto minmax(0, 1fr);
588
583
  }
589
584
 
590
- .tree-toggle[aria-expanded="true"] .tree-toggle-caret {
591
- transform: rotate(90deg);
585
+ .tree-prefix {
586
+ min-height: 38px;
587
+ display: inline-flex;
588
+ align-items: stretch;
589
+ gap: 3px;
590
+ position: relative;
592
591
  }
593
592
 
594
- .workbench-group-head {
595
- padding: 10px 14px;
596
- border: 1px solid rgba(181, 146, 99, 0.34);
597
- border-radius: 999px;
598
- background: linear-gradient(180deg, rgba(255, 250, 242, 0.98) 0%, rgba(252, 242, 230, 0.96) 100%);
593
+ .tree-prefix-toggle {
594
+ position: relative;
595
+ width: 16px;
596
+ flex: 0 0 16px;
597
+ display: inline-flex;
598
+ align-items: center;
599
+ justify-content: center;
599
600
  }
600
601
 
601
- .workbench-path-bar .tree-toggle-main {
602
- flex: 1;
603
- min-width: 0;
604
- display: grid;
605
- grid-template-columns: auto minmax(0, 1fr);
606
- align-items: center;
607
- column-gap: 10px;
608
- row-gap: 3px;
602
+ .tree-prefix-segment,
603
+ .tree-prefix-branch {
604
+ position: relative;
605
+ width: 10px;
606
+ flex: 0 0 10px;
609
607
  }
610
608
 
611
- .workbench-path-bar .workbench-group-kicker {
612
- grid-column: 1;
613
- grid-row: 1;
614
- display: inline-flex;
615
- align-items: center;
616
- padding: 3px 8px;
617
- border-radius: 999px;
618
- border: 1px solid rgba(181, 146, 99, 0.34);
619
- background: rgba(255, 255, 255, 0.56);
609
+ .tree-prefix-branch::before,
610
+ .tree-prefix-branch::after {
611
+ content: "";
612
+ position: absolute;
613
+ background: var(--tree-guide);
620
614
  }
621
615
 
622
- .workbench-path-bar .workbench-group-title {
623
- grid-column: 2;
624
- grid-row: 1;
625
- margin-top: 0;
626
- min-width: 0;
627
- overflow: hidden;
628
- text-overflow: ellipsis;
629
- white-space: nowrap;
630
- font-family: var(--font-mono);
631
- font-size: 12px;
632
- font-weight: 600;
616
+ .tree-prefix-branch::before {
617
+ left: 5px;
618
+ top: -6px;
619
+ bottom: 50%;
620
+ width: 1px;
633
621
  }
634
622
 
635
- .workbench-path-bar .tree-toggle-meta {
636
- grid-column: 2;
637
- grid-row: 2;
623
+ .tree-prefix-branch.is-mid::before {
624
+ bottom: -6px;
638
625
  }
639
626
 
640
- .workbench-group.has-active .workbench-group-head,
641
- .container-card.has-active {
642
- border-color: #c68d5a;
643
- box-shadow: 0 0 0 2px rgba(196, 85, 31, 0.08);
627
+ .tree-prefix-branch::after {
628
+ left: 5px;
629
+ top: 50%;
630
+ width: 9px;
631
+ height: 1px;
632
+ background: var(--tree-guide-soft);
644
633
  }
645
634
 
646
- .workbench-group-body {
647
- display: flex;
648
- flex-direction: column;
649
- gap: 10px;
650
- margin-left: 14px;
651
- padding-left: 12px;
652
- border-left: 1px solid var(--line-soft);
635
+ .tree-prefix-leaf {
636
+ position: relative;
637
+ width: 12px;
638
+ flex: 0 0 12px;
653
639
  }
654
640
 
655
- .workbench-group-body[hidden] {
656
- display: none;
641
+ .tree-prefix-leaf::before {
642
+ content: "";
643
+ position: absolute;
644
+ left: 1px;
645
+ top: 50%;
646
+ width: 10px;
647
+ height: 1px;
648
+ background: var(--tree-guide-soft);
649
+ transform: translateY(-0.5px);
657
650
  }
658
651
 
659
- .workbench-group-kicker,
660
- .container-card-kicker {
661
- color: var(--muted);
662
- font-size: 10px;
663
- font-weight: 700;
664
- letter-spacing: 0.7px;
665
- text-transform: uppercase;
652
+ .disclosure-toggle {
653
+ width: 16px;
654
+ height: 16px;
655
+ padding: 0;
656
+ align-self: center;
657
+ display: inline-flex;
658
+ align-items: center;
659
+ justify-content: center;
660
+ border: none;
661
+ border-radius: 6px;
662
+ color: #8c7257;
663
+ background: transparent;
664
+ transition: transform 140ms ease, background-color 140ms ease, color 140ms ease;
666
665
  }
667
666
 
668
- .workbench-group-title,
669
- .container-card-title {
670
- margin-top: 3px;
671
- font-weight: 700;
672
- font-size: 13px;
673
- line-height: 1.45;
674
- word-break: break-word;
667
+ .disclosure-toggle svg {
668
+ width: 12px;
669
+ height: 12px;
670
+ display: block;
671
+ fill: none;
672
+ stroke: currentColor;
673
+ stroke-width: 1.8;
674
+ stroke-linecap: round;
675
+ stroke-linejoin: round;
676
+ vector-effect: non-scaling-stroke;
675
677
  }
676
678
 
677
- .container-stack {
678
- display: flex;
679
- flex-direction: column;
680
- gap: 10px;
679
+ .disclosure-toggle:hover {
680
+ transform: none;
681
+ color: var(--accent-strong);
682
+ background: rgba(196, 85, 31, 0.08);
681
683
  }
682
684
 
683
- .container-card {
684
- border: 1px solid rgba(181, 146, 99, 0.52);
685
- border-radius: 16px;
686
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(249, 242, 232, 0.98) 100%);
687
- padding: 12px;
688
- display: flex;
689
- flex-direction: column;
690
- gap: 10px;
691
- box-shadow: 0 10px 22px rgba(44, 31, 15, 0.08);
685
+ .disclosure-toggle:active {
686
+ transform: none;
692
687
  }
693
688
 
694
- .container-card-head {
695
- display: flex;
696
- flex-wrap: wrap;
697
- align-items: flex-start;
698
- justify-content: space-between;
699
- gap: 12px;
689
+ .disclosure-toggle[aria-expanded="true"] {
690
+ transform: rotate(90deg);
700
691
  }
701
692
 
702
- .container-toggle {
703
- flex: 1;
704
- min-width: 0;
705
- padding: 0;
706
- border: none;
707
- background: transparent;
693
+ .disclosure-toggle[aria-expanded="true"]:hover {
694
+ transform: rotate(90deg);
695
+ }
696
+
697
+ .tree-node-button {
698
+ width: 100%;
699
+ text-align: left;
700
+ border: 1px solid rgba(181, 146, 99, 0.38);
701
+ background: rgba(255, 255, 255, 0.94);
708
702
  color: var(--text);
709
- display: flex;
710
- align-items: flex-start;
711
- justify-content: space-between;
712
- gap: 12px;
703
+ border-radius: 12px;
704
+ padding: 10px 11px;
713
705
  }
714
706
 
715
- button.container-toggle:hover,
716
- button.container-toggle:active {
707
+ .tree-node-button:hover {
708
+ transform: none;
709
+ border-color: #d1aa7f;
710
+ background: #fff8ef;
711
+ }
712
+
713
+ .tree-node-button:active {
717
714
  transform: none;
718
- background: transparent;
719
715
  }
720
716
 
721
- button.container-toggle:focus-visible {
717
+ .tree-node-button:focus-visible {
722
718
  outline: none;
723
719
  box-shadow: 0 0 0 3px rgba(196, 85, 31, 0.14);
724
720
  }
725
721
 
726
- .container-toggle-main {
727
- min-width: 0;
728
- flex: 1;
729
- display: flex;
730
- flex-direction: column;
731
- gap: 8px;
722
+ .tree-node-button.active {
723
+ border-color: #c68d5a;
724
+ background: #fff3e8;
725
+ box-shadow: inset 3px 0 0 var(--accent), 0 0 0 2px rgba(196, 85, 31, 0.14);
732
726
  }
733
727
 
734
- .container-title-row {
735
- display: flex;
736
- align-items: flex-start;
737
- gap: 10px;
738
- min-width: 0;
728
+ .tree-node-button.tree-node-tone-history .tree-node-title,
729
+ .tree-node-button.tree-node-tone-history .tree-node-meta {
730
+ color: var(--tree-history-text);
739
731
  }
740
732
 
741
- .container-title-stack {
742
- min-width: 0;
743
- display: flex;
744
- flex-direction: column;
745
- gap: 4px;
733
+ .tree-node-block.has-active > .tree-node-row > .tree-node-button:not(.active) {
734
+ border-color: #c68d5a;
735
+ box-shadow: 0 0 0 2px rgba(196, 85, 31, 0.08);
746
736
  }
747
737
 
748
- .sidebar-icon {
749
- width: 32px;
750
- height: 32px;
751
- flex: 0 0 auto;
752
- display: inline-flex;
753
- align-items: center;
754
- justify-content: center;
755
- border-radius: 11px;
756
- border: 1px solid rgba(181, 146, 99, 0.34);
757
- background: linear-gradient(180deg, rgba(255, 244, 232, 0.96) 0%, rgba(255, 252, 247, 0.9) 100%);
758
- color: var(--accent-strong);
759
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
738
+ .tree-node-button-directory,
739
+ .tree-node-button-container {
740
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(249, 242, 232, 0.98) 100%);
760
741
  }
761
742
 
762
- .sidebar-icon svg {
763
- width: 18px;
764
- height: 18px;
765
- display: block;
743
+ .tree-node-row-container .tree-node-button-container {
744
+ padding-right: 52px;
766
745
  }
767
746
 
768
- .container-card-meta {
747
+ .tree-node-main {
748
+ min-width: 0;
769
749
  display: flex;
770
- flex-wrap: wrap;
771
- gap: 8px;
772
- align-items: center;
750
+ flex-direction: column;
751
+ gap: 6px;
773
752
  }
774
753
 
775
- .sidebar-badge {
776
- display: inline-flex;
777
- align-items: center;
778
- gap: 4px;
779
- min-height: 26px;
780
- padding: 4px 10px;
781
- border-radius: 999px;
782
- border: 1px solid transparent;
783
- font-size: 11px;
754
+ .tree-node-title {
755
+ margin-top: 0;
784
756
  font-weight: 700;
785
- line-height: 1.2;
786
- }
787
-
788
- .container-status-pill {
789
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
757
+ font-size: 13px;
758
+ line-height: 1.5;
759
+ overflow-wrap: anywhere;
790
760
  }
791
761
 
792
- .container-agent-badge {
793
- background: rgba(15, 124, 114, 0.1);
794
- border-color: rgba(15, 124, 114, 0.22);
795
- color: var(--subaccent-strong);
762
+ .tree-node-button-directory .tree-node-title,
763
+ .tree-node-button-container .tree-node-title {
764
+ font-family: var(--font-mono);
765
+ font-size: 12px;
766
+ font-weight: 600;
796
767
  }
797
768
 
798
- .container-card-info {
769
+ .tree-node-meta {
799
770
  color: #7b6d5d;
800
771
  font-size: 11px;
801
772
  line-height: 1.45;
802
- white-space: nowrap;
803
- overflow: hidden;
804
- text-overflow: ellipsis;
773
+ overflow-wrap: anywhere;
805
774
  }
806
775
 
807
- .add-agent-btn {
808
- align-self: flex-start;
809
- padding: 7px 11px;
776
+ .tree-node-status {
777
+ font-weight: 700;
810
778
  font-size: 12px;
811
- border-radius: 999px;
812
- white-space: nowrap;
813
- margin-bottom: 2px;
814
- }
815
-
816
- .agent-list {
817
- display: flex;
818
- flex-direction: column;
819
- gap: 7px;
820
779
  }
821
780
 
822
- .container-card-body {
823
- margin-top: 2px;
824
- padding: 10px;
825
- border: 1px solid rgba(181, 146, 99, 0.28);
826
- border-top-color: rgba(181, 146, 99, 0.4);
827
- border-radius: 13px;
828
- background: linear-gradient(180deg, rgba(244, 238, 228, 0.88) 0%, rgba(255, 255, 255, 0.98) 100%);
829
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88);
781
+ .tree-node-status.running {
782
+ color: var(--status-running-text);
830
783
  }
831
784
 
832
- .agent-list[hidden] {
833
- display: none;
785
+ .tree-node-status.stopped {
786
+ color: var(--status-stopped-text);
834
787
  }
835
788
 
836
- .agent-item {
837
- text-align: left;
838
- width: 100%;
839
- border: 1px solid rgba(181, 146, 99, 0.38);
840
- background: rgba(255, 255, 255, 0.94);
841
- color: var(--text);
842
- border-radius: 12px;
843
- padding: 10px 11px;
844
- animation: itemIn 220ms ease both;
845
- animation-delay: calc(var(--item-index, 0) * 24ms);
789
+ .tree-node-status.history {
790
+ color: var(--tree-history-text);
846
791
  }
847
792
 
848
- .agent-item:hover {
849
- border-color: #d1aa7f;
850
- background: #fff8ef;
793
+ .tree-node-status.unknown {
794
+ color: var(--status-unknown-text);
851
795
  }
852
796
 
853
- .agent-item.active {
854
- border-color: #c68d5a;
855
- background: #fff3e8;
856
- box-shadow: inset 3px 0 0 var(--accent), 0 0 0 2px rgba(196, 85, 31, 0.14);
797
+ .tree-node-hover-menu {
798
+ position: absolute;
799
+ top: 50%;
800
+ right: 10px;
801
+ display: inline-flex;
802
+ align-items: center;
803
+ gap: 6px;
804
+ opacity: 0;
805
+ pointer-events: none;
806
+ transform: translateY(-50%) translateX(4px);
807
+ transition: opacity 140ms ease, transform 140ms ease;
857
808
  }
858
809
 
859
- .agent-name {
860
- font-size: 13px;
861
- font-weight: 700;
862
- margin-bottom: 6px;
863
- word-break: break-word;
810
+ .tree-node-row-container:hover .tree-node-hover-menu,
811
+ .tree-node-hover-menu:focus-within {
812
+ opacity: 1;
813
+ pointer-events: auto;
814
+ transform: translateY(-50%) translateX(0);
864
815
  }
865
816
 
866
- .agent-meta {
867
- display: flex;
868
- align-items: center;
869
- gap: 7px;
870
- font-size: 12px;
817
+ .tree-node-menu-item {
818
+ padding: 6px 9px;
819
+ font-size: 11px;
820
+ border-radius: 999px;
821
+ white-space: nowrap;
822
+ box-shadow: 0 10px 22px rgba(44, 31, 15, 0.12);
871
823
  }
872
824
 
873
- .agent-time {
874
- margin-top: 6px;
875
- font-size: 11px;
876
- color: #7b6d5d;
825
+ .tree-node-children[hidden] {
826
+ display: none;
877
827
  }
878
828
 
879
829
  .dir-picker-modal {
@@ -34,7 +34,7 @@
34
34
  <span>工作台</span>
35
35
  <span id="sessionCount">0 个</span>
36
36
  </div>
37
- <div id="sessionList"></div>
37
+ <div id="sessionList" role="tree" aria-label="会话树"></div>
38
38
  </aside>
39
39
  <main class="main">
40
40
  <header class="header">
@@ -2222,7 +2222,7 @@
2222
2222
  }
2223
2223
  }
2224
2224
  }
2225
- renderSessions();
2225
+ updateSidebarActiveSelection();
2226
2226
  syncUi();
2227
2227
  Promise.all([
2228
2228
  loadMessagesForSession(sessionName),
@@ -2310,173 +2310,244 @@
2310
2310
  return (parts || []).filter(Boolean).join(' · ');
2311
2311
  }
2312
2312
 
2313
- function createTreeToggle(options) {
2314
- const opts = options && typeof options === 'object' ? options : {};
2313
+ function createDisclosureButton(expanded, label) {
2315
2314
  const button = document.createElement('button');
2316
2315
  button.type = 'button';
2317
- button.className = `tree-toggle ${opts.className || ''}`.trim();
2318
- button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
2319
-
2320
- const main = document.createElement('div');
2321
- main.className = 'tree-toggle-main';
2322
-
2323
- const kicker = document.createElement('div');
2324
- kicker.className = opts.kickerClassName || 'workbench-group-kicker';
2325
- kicker.textContent = opts.kicker || '';
2326
- main.appendChild(kicker);
2327
-
2328
- const title = document.createElement('div');
2329
- title.className = opts.titleClassName || 'workbench-group-title';
2330
- title.textContent = opts.title || '';
2331
- title.title = opts.title || '';
2332
- main.appendChild(title);
2333
-
2334
- const metaText = createTreeMetaText(opts.metaParts);
2335
- if (metaText) {
2336
- const meta = document.createElement('div');
2337
- meta.className = 'tree-toggle-meta';
2338
- meta.textContent = metaText;
2339
- main.appendChild(meta);
2340
- }
2341
-
2342
- const caret = document.createElement('span');
2343
- caret.className = 'tree-toggle-caret';
2344
- caret.setAttribute('aria-hidden', 'true');
2345
- caret.textContent = '›';
2346
-
2347
- button.appendChild(main);
2348
- button.appendChild(caret);
2349
-
2350
- if (typeof opts.onClick === 'function') {
2351
- button.addEventListener('click', opts.onClick);
2352
- }
2353
-
2316
+ button.className = 'disclosure-toggle';
2317
+ button.dataset.disclosureLabel = label || '';
2318
+ button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
2319
+ button.setAttribute('aria-label', `${expanded ? '折叠' : '展开'}${label ? ` ${label}` : ''}`);
2320
+ button.innerHTML = '<svg viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M4 2.5L8 6L4 9.5"></path></svg>';
2354
2321
  return button;
2355
2322
  }
2356
2323
 
2357
- function createSidebarIcon(name) {
2358
- const icon = document.createElement('span');
2359
- icon.className = `sidebar-icon ${name ? `sidebar-icon-${name}` : ''}`.trim();
2360
- icon.setAttribute('aria-hidden', 'true');
2324
+ function createTreePrefixSegment() {
2325
+ const segment = document.createElement('span');
2326
+ segment.className = 'tree-prefix-segment';
2327
+ segment.setAttribute('aria-hidden', 'true');
2328
+ return segment;
2329
+ }
2361
2330
 
2362
- if (name === 'cube') {
2363
- icon.innerHTML = [
2364
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">',
2365
- '<path d="M12 3.5 4.75 7.5 12 11.5l7.25-4-7.25-4Z"></path>',
2366
- '<path d="M4.75 7.5v9L12 20.5l7.25-4v-9"></path>',
2367
- '<path d="M12 11.5v9"></path>',
2368
- '</svg>'
2369
- ].join('');
2331
+ function createTreePrefix(ancestorHasNext, isLastSibling, options) {
2332
+ const opts = options && typeof options === 'object' ? options : {};
2333
+ const prefix = document.createElement('div');
2334
+ prefix.className = 'tree-prefix';
2335
+
2336
+ (Array.isArray(ancestorHasNext) ? ancestorHasNext : []).forEach(function () {
2337
+ prefix.appendChild(createTreePrefixSegment());
2338
+ });
2339
+
2340
+ const branch = document.createElement('span');
2341
+ branch.className = `tree-prefix-branch ${isLastSibling ? 'is-last' : 'is-mid'}`;
2342
+ branch.setAttribute('aria-hidden', 'true');
2343
+ prefix.appendChild(branch);
2344
+
2345
+ let disclosure = null;
2346
+ let disclosureWrap = null;
2347
+ if (opts.expandable) {
2348
+ disclosure = createDisclosureButton(!!opts.expanded, opts.label || '');
2349
+ disclosureWrap = document.createElement('span');
2350
+ disclosureWrap.className = 'tree-prefix-toggle';
2351
+ disclosureWrap.appendChild(disclosure);
2352
+ prefix.appendChild(disclosureWrap);
2353
+ } else {
2354
+ const leaf = document.createElement('span');
2355
+ leaf.className = 'tree-prefix-leaf';
2356
+ leaf.setAttribute('aria-hidden', 'true');
2357
+ prefix.appendChild(leaf);
2370
2358
  }
2371
2359
 
2372
- return icon;
2360
+ return { root: prefix, disclosure, disclosureWrap };
2373
2361
  }
2374
2362
 
2375
- function createSidebarBadge(text, className) {
2376
- const badge = document.createElement('span');
2377
- badge.className = `sidebar-badge ${className || ''}`.trim();
2378
- badge.textContent = text || '';
2379
- return badge;
2363
+ function setDisclosureExpanded(control, expanded) {
2364
+ if (!control) {
2365
+ return;
2366
+ }
2367
+ const nextValue = expanded ? 'true' : 'false';
2368
+ if (control.button) {
2369
+ control.button.setAttribute('aria-expanded', nextValue);
2370
+ }
2371
+ if (control.disclosure) {
2372
+ control.disclosure.setAttribute('aria-expanded', nextValue);
2373
+ const label = control.disclosure.dataset.disclosureLabel || '';
2374
+ control.disclosure.setAttribute('aria-label', `${expanded ? '折叠' : '展开'}${label ? ` ${label}` : ''}`);
2375
+ }
2380
2376
  }
2381
2377
 
2382
- function createContainerToggle(containerGroup, expanded) {
2383
- const status = sessionStatusInfo(containerGroup && containerGroup.status);
2378
+ function createTreeItem(options) {
2379
+ const opts = options && typeof options === 'object' ? options : {};
2380
+ const row = document.createElement('div');
2381
+ row.className = `tree-node-row tree-node-row-${opts.kind || 'item'} ${opts.rowClassName || ''}`.trim();
2382
+ row.classList.toggle('has-active', !!opts.hasActive);
2383
+ row.setAttribute('role', 'none');
2384
+
2385
+ const prefixControl = createTreePrefix(opts.ancestorHasNext, !!opts.isLastSibling, {
2386
+ expandable: !!opts.expandable,
2387
+ expanded: !!opts.expanded,
2388
+ label: opts.disclosureLabel || opts.title || ''
2389
+ });
2390
+ row.appendChild(prefixControl.root);
2391
+
2384
2392
  const button = document.createElement('button');
2385
2393
  button.type = 'button';
2386
- button.className = 'tree-toggle container-toggle';
2387
- button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
2394
+ button.className = `tree-node-button tree-node-button-${opts.kind || 'item'} ${opts.className || ''}`.trim();
2395
+ button.setAttribute('role', 'treeitem');
2396
+ button.setAttribute('aria-level', String(opts.level || 1));
2397
+ if (opts.expandable) {
2398
+ button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
2399
+ }
2400
+ if (opts.kind === 'agent') {
2401
+ button.setAttribute('aria-selected', opts.active ? 'true' : 'false');
2402
+ }
2403
+ button.classList.toggle('active', !!opts.active);
2388
2404
 
2389
2405
  const main = document.createElement('div');
2390
- main.className = 'container-toggle-main';
2391
-
2392
- const titleRow = document.createElement('div');
2393
- titleRow.className = 'container-title-row';
2394
- titleRow.appendChild(createSidebarIcon('cube'));
2395
-
2396
- const titleStack = document.createElement('div');
2397
- titleStack.className = 'container-title-stack';
2398
-
2399
- const kicker = document.createElement('div');
2400
- kicker.className = 'container-card-kicker';
2401
- kicker.textContent = '容器';
2406
+ main.className = 'tree-node-main';
2402
2407
 
2403
2408
  const title = document.createElement('div');
2404
- title.className = 'container-card-title';
2405
- title.textContent = containerGroup && containerGroup.containerName ? containerGroup.containerName : '';
2406
- title.title = title.textContent;
2407
-
2408
- titleStack.appendChild(kicker);
2409
- titleStack.appendChild(title);
2410
- titleRow.appendChild(titleStack);
2411
- main.appendChild(titleRow);
2409
+ title.className = opts.titleClassName || 'tree-node-title';
2410
+ title.textContent = opts.title || '';
2411
+ title.title = opts.title || '';
2412
+ main.appendChild(title);
2412
2413
 
2413
- const meta = document.createElement('div');
2414
- meta.className = 'container-card-meta';
2415
- meta.appendChild(createSidebarBadge(status.label, `session-status ${status.tone} container-status-pill`));
2416
- meta.appendChild(createSidebarBadge(`${containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions.length : 0} 个 AGENT`, 'container-agent-badge'));
2417
- main.appendChild(meta);
2418
-
2419
- const infoText = createTreeMetaText([
2420
- containerGroup && containerGroup.image ? containerGroup.image : '',
2421
- formatDateTime(containerGroup && containerGroup.updatedAt) || '暂无更新'
2422
- ]);
2423
- if (infoText) {
2424
- const info = document.createElement('div');
2425
- info.className = 'container-card-info';
2426
- info.textContent = infoText;
2427
- info.title = infoText;
2428
- main.appendChild(info);
2429
- }
2430
-
2431
- const caret = document.createElement('span');
2432
- caret.className = 'tree-toggle-caret';
2433
- caret.setAttribute('aria-hidden', 'true');
2434
- caret.textContent = '›';
2414
+ if (opts.meta) {
2415
+ const meta = document.createElement('div');
2416
+ meta.className = `tree-node-meta ${opts.metaClassName || ''}`.trim();
2417
+ meta.textContent = opts.meta;
2418
+ main.appendChild(meta);
2419
+ }
2435
2420
 
2436
2421
  button.appendChild(main);
2437
- button.appendChild(caret);
2422
+ row.appendChild(button);
2423
+ if (opts.overlayNode) {
2424
+ row.appendChild(opts.overlayNode);
2425
+ }
2438
2426
 
2439
- return button;
2427
+ const control = {
2428
+ root: row,
2429
+ button,
2430
+ disclosure: prefixControl.disclosure
2431
+ };
2432
+ setDisclosureExpanded(control, !!opts.expanded);
2433
+ return control;
2440
2434
  }
2441
2435
 
2442
- function createAgentRow(session, index) {
2443
- const status = sessionStatusInfo(session.status);
2444
- const btn = document.createElement('button');
2445
- btn.type = 'button';
2446
- btn.className = 'agent-item';
2447
- btn.dataset.sessionName = session.name;
2448
- btn.style.setProperty('--item-index', String(index));
2449
- btn.classList.toggle('active', state.active === session.name);
2450
-
2451
- const sessionName = document.createElement('div');
2452
- sessionName.className = 'agent-name';
2453
- sessionName.textContent = session.agentName || session.name;
2454
-
2455
- const meta = document.createElement('div');
2456
- meta.className = 'agent-meta';
2436
+ function buildSessionTreeNodes(grouped) {
2437
+ return (Array.isArray(grouped) ? grouped : []).map(function (directoryGroup) {
2438
+ return {
2439
+ kind: 'directory',
2440
+ title: directoryGroup.path,
2441
+ disclosureLabel: directoryGroup.path,
2442
+ expanded: isDirectoryExpanded(directoryGroup),
2443
+ hasActive: directoryContainsActiveSession(directoryGroup),
2444
+ onToggle: function (expanded) {
2445
+ setSidebarDirectoryExpanded(directoryGroup.path, expanded);
2446
+ },
2447
+ children: (Array.isArray(directoryGroup.containers) ? directoryGroup.containers : []).map(function (containerGroup) {
2448
+ const status = sessionStatusInfo(containerGroup && containerGroup.status);
2449
+ const historyClassName = status.tone === 'history' ? 'tree-node-tone-history' : '';
2450
+ const containerName = String(containerGroup && containerGroup.containerName ? containerGroup.containerName : '').trim() || '未命名容器';
2451
+ const hoverMenu = document.createElement('div');
2452
+ hoverMenu.className = 'tree-node-hover-menu';
2453
+
2454
+ const addAgentBtn = document.createElement('button');
2455
+ addAgentBtn.type = 'button';
2456
+ addAgentBtn.className = 'secondary tree-node-menu-item';
2457
+ addAgentBtn.textContent = '新建AGENT';
2458
+ addAgentBtn.addEventListener('click', function (event) {
2459
+ event.stopPropagation();
2460
+ createAgentSession(containerName);
2461
+ });
2462
+ hoverMenu.appendChild(addAgentBtn);
2463
+
2464
+ return {
2465
+ kind: 'container',
2466
+ title: containerName,
2467
+ meta: status.label,
2468
+ metaClassName: `tree-node-status ${status.tone}`,
2469
+ className: historyClassName,
2470
+ disclosureLabel: containerName,
2471
+ expanded: isContainerExpanded(containerGroup),
2472
+ hasActive: containerContainsActiveSession(containerGroup),
2473
+ overlayNode: hoverMenu,
2474
+ onToggle: function (expanded) {
2475
+ setSidebarContainerExpanded(containerGroup.containerName, expanded);
2476
+ },
2477
+ children: (Array.isArray(containerGroup.sessions) ? containerGroup.sessions : []).map(function (session) {
2478
+ return {
2479
+ kind: 'agent',
2480
+ title: session.agentName || session.name,
2481
+ meta: formatDateTime(session.updatedAt) || '暂无更新',
2482
+ className: historyClassName,
2483
+ active: state.active === session.name,
2484
+ sessionName: session.name
2485
+ };
2486
+ })
2487
+ };
2488
+ })
2489
+ };
2490
+ });
2491
+ }
2457
2492
 
2458
- const statusBadge = document.createElement('span');
2459
- statusBadge.className = `session-status ${status.tone}`;
2460
- statusBadge.textContent = status.label;
2493
+ function renderSessionTreeNodes(nodes, parentNode, ancestorHasNext, itemCounter) {
2494
+ (Array.isArray(nodes) ? nodes : []).forEach(function (node, index) {
2495
+ const isLastSibling = index === nodes.length - 1;
2496
+ const item = createTreeItem({
2497
+ kind: node.kind,
2498
+ title: node.title,
2499
+ meta: node.meta,
2500
+ className: node.className,
2501
+ metaClassName: node.metaClassName,
2502
+ disclosureLabel: node.disclosureLabel,
2503
+ expandable: Array.isArray(node.children) && node.children.length > 0,
2504
+ expanded: !!node.expanded,
2505
+ active: !!node.active,
2506
+ hasActive: !!node.hasActive,
2507
+ level: ancestorHasNext.length + 1,
2508
+ ancestorHasNext: ancestorHasNext,
2509
+ isLastSibling: isLastSibling,
2510
+ overlayNode: node.overlayNode
2511
+ });
2461
2512
 
2462
- const messageCount = document.createElement('span');
2463
- messageCount.className = 'session-count';
2464
- messageCount.textContent = `${safeMessageCount(session.messageCount)} 条`;
2513
+ if (node.kind === 'agent') {
2514
+ item.button.dataset.sessionName = node.sessionName || '';
2515
+ item.button.style.setProperty('--item-index', String(itemCounter.value));
2516
+ itemCounter.value += 1;
2517
+ state.sessionNodeMap.set(node.sessionName, item.button);
2518
+ item.button.addEventListener('click', function () {
2519
+ handleSessionItemClick(node.sessionName || '');
2520
+ });
2521
+ parentNode.appendChild(item.root);
2522
+ return;
2523
+ }
2465
2524
 
2466
- meta.appendChild(statusBadge);
2467
- meta.appendChild(messageCount);
2525
+ const block = document.createElement('section');
2526
+ block.className = `tree-node-block tree-node-block-${node.kind}`;
2527
+ block.classList.toggle('has-active', !!node.hasActive);
2528
+ block.appendChild(item.root);
2468
2529
 
2469
- const time = document.createElement('div');
2470
- time.className = 'agent-time';
2471
- time.textContent = formatDateTime(session.updatedAt) || '暂无更新';
2530
+ const childrenNode = document.createElement('div');
2531
+ childrenNode.className = `tree-node-children tree-node-children-${node.kind}`;
2532
+ childrenNode.setAttribute('role', 'group');
2533
+ childrenNode.hidden = !node.expanded;
2534
+ renderSessionTreeNodes(node.children || [], childrenNode, ancestorHasNext.concat(!isLastSibling), itemCounter);
2535
+ block.appendChild(childrenNode);
2536
+ parentNode.appendChild(block);
2472
2537
 
2473
- btn.appendChild(sessionName);
2474
- btn.appendChild(meta);
2475
- btn.appendChild(time);
2476
- btn.addEventListener('click', function () {
2477
- handleSessionItemClick(btn.dataset.sessionName || '');
2538
+ const toggleNode = function () {
2539
+ const nextExpanded = childrenNode.hidden;
2540
+ if (typeof node.onToggle === 'function') {
2541
+ node.onToggle(nextExpanded);
2542
+ }
2543
+ setDisclosureExpanded(item, nextExpanded);
2544
+ childrenNode.hidden = !nextExpanded;
2545
+ };
2546
+ item.button.addEventListener('click', toggleNode);
2547
+ if (item.disclosure) {
2548
+ item.disclosure.addEventListener('click', toggleNode);
2549
+ }
2478
2550
  });
2479
- return btn;
2480
2551
  }
2481
2552
 
2482
2553
  function scrollActiveSessionIntoView() {
@@ -2492,6 +2563,37 @@
2492
2563
  }
2493
2564
  }
2494
2565
 
2566
+ function updateSidebarActiveSelection() {
2567
+ state.sessionNodeMap.forEach(function (buttonNode, sessionName) {
2568
+ const isActive = sessionName === state.active;
2569
+ if (!buttonNode) {
2570
+ return;
2571
+ }
2572
+ buttonNode.classList.toggle('active', isActive);
2573
+ buttonNode.setAttribute('aria-selected', isActive ? 'true' : 'false');
2574
+ });
2575
+
2576
+ if (!sessionList) {
2577
+ return;
2578
+ }
2579
+
2580
+ sessionList.querySelectorAll('.tree-node-block.has-active').forEach(function (blockNode) {
2581
+ blockNode.classList.remove('has-active');
2582
+ });
2583
+
2584
+ const activeNode = state.sessionNodeMap.get(state.active);
2585
+ let cursor = activeNode ? activeNode.parentElement : null;
2586
+ while (cursor && cursor !== sessionList) {
2587
+ if (cursor.classList && cursor.classList.contains('tree-node-children')) {
2588
+ const blockNode = cursor.parentElement;
2589
+ if (blockNode && blockNode.classList && blockNode.classList.contains('tree-node-block')) {
2590
+ blockNode.classList.add('has-active');
2591
+ }
2592
+ }
2593
+ cursor = cursor.parentElement;
2594
+ }
2595
+ }
2596
+
2495
2597
  function renderSessions() {
2496
2598
  const containerCount = new Set(state.sessions.map(function (session) {
2497
2599
  return session && session.containerName ? session.containerName : '';
@@ -2521,88 +2623,10 @@
2521
2623
  state.sessionRenderMode = 'tree';
2522
2624
 
2523
2625
  const grouped = groupSessionsByDirectory(state.sessions);
2524
- let itemIndex = 0;
2525
-
2526
- grouped.forEach(function (directoryGroup) {
2527
- const directoryExpanded = isDirectoryExpanded(directoryGroup);
2528
- const directoryHasActive = directoryContainsActiveSession(directoryGroup);
2529
- const group = document.createElement('section');
2530
- group.className = 'workbench-group';
2531
- group.classList.toggle('has-active', directoryHasActive);
2532
-
2533
- const groupHead = createTreeToggle({
2534
- className: 'workbench-group-head workbench-path-bar',
2535
- kickerClassName: 'workbench-group-kicker',
2536
- titleClassName: 'workbench-group-title',
2537
- kicker: '路径分组',
2538
- title: directoryGroup.path,
2539
- expanded: directoryExpanded,
2540
- metaParts: [
2541
- `${directoryGroup.containers.length} 容器`,
2542
- `${directoryGroup.containers.reduce(function (count, containerGroup) {
2543
- return count + containerGroup.sessions.length;
2544
- }, 0)} AGENT`,
2545
- formatDateTime(directoryGroup.updatedAt) || '暂无更新'
2546
- ]
2547
- });
2548
- group.appendChild(groupHead);
2549
-
2550
- const containerStack = document.createElement('div');
2551
- containerStack.className = 'container-stack workbench-group-body';
2552
- containerStack.hidden = !directoryExpanded;
2553
- groupHead.addEventListener('click', function () {
2554
- const nextExpanded = containerStack.hidden;
2555
- setSidebarDirectoryExpanded(directoryGroup.path, nextExpanded);
2556
- groupHead.setAttribute('aria-expanded', nextExpanded ? 'true' : 'false');
2557
- containerStack.hidden = !nextExpanded;
2558
- });
2559
-
2560
- directoryGroup.containers.forEach(function (containerGroup) {
2561
- const containerExpanded = isContainerExpanded(containerGroup);
2562
- const containerHasActive = containerContainsActiveSession(containerGroup);
2563
- const containerCard = document.createElement('section');
2564
- containerCard.className = 'container-card';
2565
- containerCard.classList.toggle('has-active', containerHasActive);
2566
-
2567
- const containerHead = document.createElement('div');
2568
- containerHead.className = 'container-card-head';
2569
-
2570
- const containerToggle = createContainerToggle(containerGroup, containerExpanded);
2571
-
2572
- const addAgentBtn = document.createElement('button');
2573
- addAgentBtn.type = 'button';
2574
- addAgentBtn.className = 'secondary add-agent-btn';
2575
- addAgentBtn.textContent = '新建AGENT';
2576
- addAgentBtn.addEventListener('click', function () {
2577
- createAgentSession(containerGroup.containerName);
2578
- });
2579
-
2580
- containerHead.appendChild(containerToggle);
2581
- containerCard.appendChild(containerHead);
2582
-
2583
- const agentList = document.createElement('div');
2584
- agentList.className = 'agent-list container-card-body';
2585
- agentList.hidden = !containerExpanded;
2586
- containerToggle.addEventListener('click', function () {
2587
- const nextExpanded = agentList.hidden;
2588
- setSidebarContainerExpanded(containerGroup.containerName, nextExpanded);
2589
- containerToggle.setAttribute('aria-expanded', nextExpanded ? 'true' : 'false');
2590
- agentList.hidden = !nextExpanded;
2591
- });
2592
- agentList.appendChild(addAgentBtn);
2593
- containerGroup.sessions.forEach(function (session) {
2594
- const row = createAgentRow(session, itemIndex);
2595
- state.sessionNodeMap.set(session.name, row);
2596
- agentList.appendChild(row);
2597
- itemIndex += 1;
2598
- });
2599
- containerCard.appendChild(agentList);
2600
- containerStack.appendChild(containerCard);
2601
- });
2602
-
2603
- group.appendChild(containerStack);
2604
- sessionList.appendChild(group);
2605
- });
2626
+ const treeNodes = buildSessionTreeNodes(grouped);
2627
+ const itemCounter = { value: 0 };
2628
+ renderSessionTreeNodes(treeNodes, sessionList, [], itemCounter);
2629
+ updateSidebarActiveSelection();
2606
2630
 
2607
2631
  scrollActiveSessionIntoView();
2608
2632
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.7.14",
3
+ "version": "5.7.20",
4
4
  "imageVersion": "1.9.0-common",
5
5
  "playwrightCliVersion": "0.1.1",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",