@xcanwin/manyoyo 5.7.13 → 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 {
@@ -1516,6 +1466,36 @@ details.trace-card > .trace-card-summary {
1516
1466
  cursor: pointer;
1517
1467
  }
1518
1468
 
1469
+ .trace-card.trace-card-compact {
1470
+ border-left-width: 2px;
1471
+ border-radius: 999px;
1472
+ background: rgba(255, 251, 245, 0.78);
1473
+ box-shadow: none;
1474
+ }
1475
+
1476
+ .trace-card.trace-card-compact .trace-card-summary {
1477
+ padding: 5px 10px;
1478
+ gap: 7px;
1479
+ }
1480
+
1481
+ .trace-card.trace-card-compact .trace-card-badge {
1482
+ min-width: 0;
1483
+ padding: 2px 6px;
1484
+ background: rgba(31, 26, 20, 0.06);
1485
+ font-size: 10px;
1486
+ }
1487
+
1488
+ .trace-card.trace-card-compact .trace-card-title {
1489
+ font-size: 11px;
1490
+ font-weight: 500;
1491
+ color: #6e6256;
1492
+ }
1493
+
1494
+ .trace-card.trace-card-compact .trace-card-phase {
1495
+ font-size: 10px;
1496
+ color: #8a7d70;
1497
+ }
1498
+
1519
1499
  .trace-card-summary::-webkit-details-marker {
1520
1500
  display: none;
1521
1501
  }
@@ -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">
@@ -354,6 +354,34 @@
354
354
  return 'neutral';
355
355
  }
356
356
 
357
+ function shouldCompactTraceEvent(traceEvent) {
358
+ const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
359
+ const phase = event.phase ? String(event.phase) : '';
360
+ const kind = event.kind ? String(event.kind) : '';
361
+ if (phase !== 'completed') {
362
+ return false;
363
+ }
364
+ return kind === 'command' || kind === 'mcp' || kind === 'tool';
365
+ }
366
+
367
+ function buildCompactTraceText(traceEvent) {
368
+ const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
369
+ if (event.kind === 'command') {
370
+ const suffix = typeof event.exitCode === 'number'
371
+ ? `exit ${event.exitCode}`
372
+ : (event.status ? String(event.status) : 'completed');
373
+ return `${event.command || event.text || '命令'} · ${suffix}`;
374
+ }
375
+ if (event.kind === 'mcp') {
376
+ const toolLabel = [event.server, event.tool].filter(Boolean).join('.') || event.text || 'MCP';
377
+ return event.argumentSummary ? `${toolLabel} · ${event.argumentSummary}` : toolLabel;
378
+ }
379
+ if (event.kind === 'tool') {
380
+ return event.toolName || event.text || '工具';
381
+ }
382
+ return event.text || '事件';
383
+ }
384
+
357
385
  function appendTraceCardBody(cardBody, label, value) {
358
386
  const text = stringifyPrettyJson(value).trim();
359
387
  if (!text) {
@@ -377,11 +405,12 @@
377
405
 
378
406
  function createTraceEventCard(traceEvent) {
379
407
  const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
408
+ const compact = shouldCompactTraceEvent(event);
380
409
  const bodyParts = [];
381
- if (event.kind === 'command' && event.command) {
410
+ if (!compact && event.kind === 'command' && event.command) {
382
411
  bodyParts.push({ label: '命令', value: event.command });
383
412
  }
384
- if (event.kind === 'mcp') {
413
+ if (!compact && event.kind === 'mcp') {
385
414
  if (event.argumentSummary) {
386
415
  bodyParts.push({ label: '参数摘要', value: event.argumentSummary });
387
416
  }
@@ -395,7 +424,7 @@
395
424
  bodyParts.push({ label: '错误', value: event.error });
396
425
  }
397
426
  }
398
- if (event.kind === 'tool' && event.toolName) {
427
+ if (!compact && event.kind === 'tool' && event.toolName) {
399
428
  bodyParts.push({ label: '工具', value: event.toolName });
400
429
  }
401
430
  if ((event.kind === 'agent_message' || event.kind === 'status' || event.kind === 'error') && event.detail) {
@@ -404,7 +433,7 @@
404
433
 
405
434
  const hasBody = bodyParts.length > 0;
406
435
  const card = document.createElement(hasBody ? 'details' : 'div');
407
- card.className = 'trace-card trace-tone-' + resolveTraceTone(event);
436
+ card.className = 'trace-card trace-tone-' + resolveTraceTone(event) + (compact ? ' trace-card-compact' : '');
408
437
  if (hasBody && event.kind === 'error') {
409
438
  card.open = true;
410
439
  }
@@ -419,7 +448,7 @@
419
448
 
420
449
  const title = document.createElement('span');
421
450
  title.className = 'trace-card-title';
422
- title.textContent = event && event.text ? String(event.text) : '事件';
451
+ title.textContent = compact ? buildCompactTraceText(event) : (event && event.text ? String(event.text) : '事件');
423
452
  header.appendChild(title);
424
453
 
425
454
  const phaseText = humanizeTracePhase(event);
@@ -2193,7 +2222,7 @@
2193
2222
  }
2194
2223
  }
2195
2224
  }
2196
- renderSessions();
2225
+ updateSidebarActiveSelection();
2197
2226
  syncUi();
2198
2227
  Promise.all([
2199
2228
  loadMessagesForSession(sessionName),
@@ -2281,173 +2310,244 @@
2281
2310
  return (parts || []).filter(Boolean).join(' · ');
2282
2311
  }
2283
2312
 
2284
- function createTreeToggle(options) {
2285
- const opts = options && typeof options === 'object' ? options : {};
2313
+ function createDisclosureButton(expanded, label) {
2286
2314
  const button = document.createElement('button');
2287
2315
  button.type = 'button';
2288
- button.className = `tree-toggle ${opts.className || ''}`.trim();
2289
- button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
2290
-
2291
- const main = document.createElement('div');
2292
- main.className = 'tree-toggle-main';
2293
-
2294
- const kicker = document.createElement('div');
2295
- kicker.className = opts.kickerClassName || 'workbench-group-kicker';
2296
- kicker.textContent = opts.kicker || '';
2297
- main.appendChild(kicker);
2298
-
2299
- const title = document.createElement('div');
2300
- title.className = opts.titleClassName || 'workbench-group-title';
2301
- title.textContent = opts.title || '';
2302
- title.title = opts.title || '';
2303
- main.appendChild(title);
2304
-
2305
- const metaText = createTreeMetaText(opts.metaParts);
2306
- if (metaText) {
2307
- const meta = document.createElement('div');
2308
- meta.className = 'tree-toggle-meta';
2309
- meta.textContent = metaText;
2310
- main.appendChild(meta);
2311
- }
2312
-
2313
- const caret = document.createElement('span');
2314
- caret.className = 'tree-toggle-caret';
2315
- caret.setAttribute('aria-hidden', 'true');
2316
- caret.textContent = '›';
2317
-
2318
- button.appendChild(main);
2319
- button.appendChild(caret);
2320
-
2321
- if (typeof opts.onClick === 'function') {
2322
- button.addEventListener('click', opts.onClick);
2323
- }
2324
-
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>';
2325
2321
  return button;
2326
2322
  }
2327
2323
 
2328
- function createSidebarIcon(name) {
2329
- const icon = document.createElement('span');
2330
- icon.className = `sidebar-icon ${name ? `sidebar-icon-${name}` : ''}`.trim();
2331
- 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
+ }
2332
2330
 
2333
- if (name === 'cube') {
2334
- icon.innerHTML = [
2335
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">',
2336
- '<path d="M12 3.5 4.75 7.5 12 11.5l7.25-4-7.25-4Z"></path>',
2337
- '<path d="M4.75 7.5v9L12 20.5l7.25-4v-9"></path>',
2338
- '<path d="M12 11.5v9"></path>',
2339
- '</svg>'
2340
- ].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);
2341
2358
  }
2342
2359
 
2343
- return icon;
2360
+ return { root: prefix, disclosure, disclosureWrap };
2344
2361
  }
2345
2362
 
2346
- function createSidebarBadge(text, className) {
2347
- const badge = document.createElement('span');
2348
- badge.className = `sidebar-badge ${className || ''}`.trim();
2349
- badge.textContent = text || '';
2350
- 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
+ }
2351
2376
  }
2352
2377
 
2353
- function createContainerToggle(containerGroup, expanded) {
2354
- 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
+
2355
2392
  const button = document.createElement('button');
2356
2393
  button.type = 'button';
2357
- button.className = 'tree-toggle container-toggle';
2358
- 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);
2359
2404
 
2360
2405
  const main = document.createElement('div');
2361
- main.className = 'container-toggle-main';
2362
-
2363
- const titleRow = document.createElement('div');
2364
- titleRow.className = 'container-title-row';
2365
- titleRow.appendChild(createSidebarIcon('cube'));
2366
-
2367
- const titleStack = document.createElement('div');
2368
- titleStack.className = 'container-title-stack';
2369
-
2370
- const kicker = document.createElement('div');
2371
- kicker.className = 'container-card-kicker';
2372
- kicker.textContent = '容器';
2406
+ main.className = 'tree-node-main';
2373
2407
 
2374
2408
  const title = document.createElement('div');
2375
- title.className = 'container-card-title';
2376
- title.textContent = containerGroup && containerGroup.containerName ? containerGroup.containerName : '';
2377
- title.title = title.textContent;
2378
-
2379
- titleStack.appendChild(kicker);
2380
- titleStack.appendChild(title);
2381
- titleRow.appendChild(titleStack);
2382
- 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);
2383
2413
 
2384
- const meta = document.createElement('div');
2385
- meta.className = 'container-card-meta';
2386
- meta.appendChild(createSidebarBadge(status.label, `session-status ${status.tone} container-status-pill`));
2387
- meta.appendChild(createSidebarBadge(`${containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions.length : 0} 个 AGENT`, 'container-agent-badge'));
2388
- main.appendChild(meta);
2389
-
2390
- const infoText = createTreeMetaText([
2391
- containerGroup && containerGroup.image ? containerGroup.image : '',
2392
- formatDateTime(containerGroup && containerGroup.updatedAt) || '暂无更新'
2393
- ]);
2394
- if (infoText) {
2395
- const info = document.createElement('div');
2396
- info.className = 'container-card-info';
2397
- info.textContent = infoText;
2398
- info.title = infoText;
2399
- main.appendChild(info);
2400
- }
2401
-
2402
- const caret = document.createElement('span');
2403
- caret.className = 'tree-toggle-caret';
2404
- caret.setAttribute('aria-hidden', 'true');
2405
- 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
+ }
2406
2420
 
2407
2421
  button.appendChild(main);
2408
- button.appendChild(caret);
2422
+ row.appendChild(button);
2423
+ if (opts.overlayNode) {
2424
+ row.appendChild(opts.overlayNode);
2425
+ }
2409
2426
 
2410
- return button;
2427
+ const control = {
2428
+ root: row,
2429
+ button,
2430
+ disclosure: prefixControl.disclosure
2431
+ };
2432
+ setDisclosureExpanded(control, !!opts.expanded);
2433
+ return control;
2411
2434
  }
2412
2435
 
2413
- function createAgentRow(session, index) {
2414
- const status = sessionStatusInfo(session.status);
2415
- const btn = document.createElement('button');
2416
- btn.type = 'button';
2417
- btn.className = 'agent-item';
2418
- btn.dataset.sessionName = session.name;
2419
- btn.style.setProperty('--item-index', String(index));
2420
- btn.classList.toggle('active', state.active === session.name);
2421
-
2422
- const sessionName = document.createElement('div');
2423
- sessionName.className = 'agent-name';
2424
- sessionName.textContent = session.agentName || session.name;
2425
-
2426
- const meta = document.createElement('div');
2427
- 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
+ }
2428
2492
 
2429
- const statusBadge = document.createElement('span');
2430
- statusBadge.className = `session-status ${status.tone}`;
2431
- 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
+ });
2432
2512
 
2433
- const messageCount = document.createElement('span');
2434
- messageCount.className = 'session-count';
2435
- 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
+ }
2436
2524
 
2437
- meta.appendChild(statusBadge);
2438
- 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);
2439
2529
 
2440
- const time = document.createElement('div');
2441
- time.className = 'agent-time';
2442
- 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);
2443
2537
 
2444
- btn.appendChild(sessionName);
2445
- btn.appendChild(meta);
2446
- btn.appendChild(time);
2447
- btn.addEventListener('click', function () {
2448
- 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
+ }
2449
2550
  });
2450
- return btn;
2451
2551
  }
2452
2552
 
2453
2553
  function scrollActiveSessionIntoView() {
@@ -2463,6 +2563,37 @@
2463
2563
  }
2464
2564
  }
2465
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
+
2466
2597
  function renderSessions() {
2467
2598
  const containerCount = new Set(state.sessions.map(function (session) {
2468
2599
  return session && session.containerName ? session.containerName : '';
@@ -2492,88 +2623,10 @@
2492
2623
  state.sessionRenderMode = 'tree';
2493
2624
 
2494
2625
  const grouped = groupSessionsByDirectory(state.sessions);
2495
- let itemIndex = 0;
2496
-
2497
- grouped.forEach(function (directoryGroup) {
2498
- const directoryExpanded = isDirectoryExpanded(directoryGroup);
2499
- const directoryHasActive = directoryContainsActiveSession(directoryGroup);
2500
- const group = document.createElement('section');
2501
- group.className = 'workbench-group';
2502
- group.classList.toggle('has-active', directoryHasActive);
2503
-
2504
- const groupHead = createTreeToggle({
2505
- className: 'workbench-group-head workbench-path-bar',
2506
- kickerClassName: 'workbench-group-kicker',
2507
- titleClassName: 'workbench-group-title',
2508
- kicker: '路径分组',
2509
- title: directoryGroup.path,
2510
- expanded: directoryExpanded,
2511
- metaParts: [
2512
- `${directoryGroup.containers.length} 容器`,
2513
- `${directoryGroup.containers.reduce(function (count, containerGroup) {
2514
- return count + containerGroup.sessions.length;
2515
- }, 0)} AGENT`,
2516
- formatDateTime(directoryGroup.updatedAt) || '暂无更新'
2517
- ]
2518
- });
2519
- group.appendChild(groupHead);
2520
-
2521
- const containerStack = document.createElement('div');
2522
- containerStack.className = 'container-stack workbench-group-body';
2523
- containerStack.hidden = !directoryExpanded;
2524
- groupHead.addEventListener('click', function () {
2525
- const nextExpanded = containerStack.hidden;
2526
- setSidebarDirectoryExpanded(directoryGroup.path, nextExpanded);
2527
- groupHead.setAttribute('aria-expanded', nextExpanded ? 'true' : 'false');
2528
- containerStack.hidden = !nextExpanded;
2529
- });
2530
-
2531
- directoryGroup.containers.forEach(function (containerGroup) {
2532
- const containerExpanded = isContainerExpanded(containerGroup);
2533
- const containerHasActive = containerContainsActiveSession(containerGroup);
2534
- const containerCard = document.createElement('section');
2535
- containerCard.className = 'container-card';
2536
- containerCard.classList.toggle('has-active', containerHasActive);
2537
-
2538
- const containerHead = document.createElement('div');
2539
- containerHead.className = 'container-card-head';
2540
-
2541
- const containerToggle = createContainerToggle(containerGroup, containerExpanded);
2542
-
2543
- const addAgentBtn = document.createElement('button');
2544
- addAgentBtn.type = 'button';
2545
- addAgentBtn.className = 'secondary add-agent-btn';
2546
- addAgentBtn.textContent = '新建AGENT';
2547
- addAgentBtn.addEventListener('click', function () {
2548
- createAgentSession(containerGroup.containerName);
2549
- });
2550
-
2551
- containerHead.appendChild(containerToggle);
2552
- containerCard.appendChild(containerHead);
2553
-
2554
- const agentList = document.createElement('div');
2555
- agentList.className = 'agent-list container-card-body';
2556
- agentList.hidden = !containerExpanded;
2557
- containerToggle.addEventListener('click', function () {
2558
- const nextExpanded = agentList.hidden;
2559
- setSidebarContainerExpanded(containerGroup.containerName, nextExpanded);
2560
- containerToggle.setAttribute('aria-expanded', nextExpanded ? 'true' : 'false');
2561
- agentList.hidden = !nextExpanded;
2562
- });
2563
- agentList.appendChild(addAgentBtn);
2564
- containerGroup.sessions.forEach(function (session) {
2565
- const row = createAgentRow(session, itemIndex);
2566
- state.sessionNodeMap.set(session.name, row);
2567
- agentList.appendChild(row);
2568
- itemIndex += 1;
2569
- });
2570
- containerCard.appendChild(agentList);
2571
- containerStack.appendChild(containerCard);
2572
- });
2573
-
2574
- group.appendChild(containerStack);
2575
- sessionList.appendChild(group);
2576
- });
2626
+ const treeNodes = buildSessionTreeNodes(grouped);
2627
+ const itemCounter = { value: 0 };
2628
+ renderSessionTreeNodes(treeNodes, sessionList, [], itemCounter);
2629
+ updateSidebarActiveSelection();
2577
2630
 
2578
2631
  scrollActiveSessionIntoView();
2579
2632
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.7.13",
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",