claudehq 1.0.2 → 1.0.3
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/core/config.js +24 -0
- package/lib/core/event-bus.js +18 -0
- package/lib/data/orchestration.js +941 -0
- package/lib/index.js +100 -19
- package/lib/orchestration/executor.js +635 -0
- package/lib/routes/api.js +399 -0
- package/package.json +1 -1
- package/public/index.html +1240 -0
- package/lib/server.js +0 -9364
package/public/index.html
CHANGED
|
@@ -2672,6 +2672,558 @@
|
|
|
2672
2672
|
justify-content: center;
|
|
2673
2673
|
font-size: 24px;
|
|
2674
2674
|
}
|
|
2675
|
+
|
|
2676
|
+
/* Orchestration Panel Styles */
|
|
2677
|
+
.orchestration-panel {
|
|
2678
|
+
position: fixed;
|
|
2679
|
+
top: 0;
|
|
2680
|
+
right: 0;
|
|
2681
|
+
width: 480px;
|
|
2682
|
+
height: 100vh;
|
|
2683
|
+
background: var(--bg-secondary);
|
|
2684
|
+
border-left: 1px solid var(--border);
|
|
2685
|
+
transform: translateX(100%);
|
|
2686
|
+
transition: transform 0.2s ease;
|
|
2687
|
+
z-index: 100;
|
|
2688
|
+
display: flex;
|
|
2689
|
+
flex-direction: column;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
.orchestration-panel.open {
|
|
2693
|
+
transform: translateX(0);
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
.orchestration-header {
|
|
2697
|
+
display: flex;
|
|
2698
|
+
align-items: center;
|
|
2699
|
+
justify-content: space-between;
|
|
2700
|
+
padding: 16px;
|
|
2701
|
+
border-bottom: 1px solid var(--border);
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
.orchestration-header h3 {
|
|
2705
|
+
display: flex;
|
|
2706
|
+
align-items: center;
|
|
2707
|
+
gap: 8px;
|
|
2708
|
+
font-size: 14px;
|
|
2709
|
+
font-weight: 600;
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
.orchestration-content {
|
|
2713
|
+
flex: 1;
|
|
2714
|
+
overflow-y: auto;
|
|
2715
|
+
padding: 16px;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
.orchestration-list {
|
|
2719
|
+
display: flex;
|
|
2720
|
+
flex-direction: column;
|
|
2721
|
+
gap: 12px;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
.orchestration-card {
|
|
2725
|
+
background: var(--bg-tertiary);
|
|
2726
|
+
border: 1px solid var(--border);
|
|
2727
|
+
border-radius: 8px;
|
|
2728
|
+
padding: 12px;
|
|
2729
|
+
cursor: pointer;
|
|
2730
|
+
transition: all 0.15s ease;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
.orchestration-card:hover {
|
|
2734
|
+
border-color: var(--accent);
|
|
2735
|
+
background: var(--bg-hover);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
.orchestration-card.active {
|
|
2739
|
+
border-color: var(--accent);
|
|
2740
|
+
box-shadow: 0 0 0 1px var(--accent);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
.orchestration-card-header {
|
|
2744
|
+
display: flex;
|
|
2745
|
+
align-items: center;
|
|
2746
|
+
justify-content: space-between;
|
|
2747
|
+
margin-bottom: 8px;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
.orchestration-name {
|
|
2751
|
+
font-weight: 600;
|
|
2752
|
+
font-size: 14px;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
.orchestration-status {
|
|
2756
|
+
font-size: 11px;
|
|
2757
|
+
padding: 2px 8px;
|
|
2758
|
+
border-radius: 10px;
|
|
2759
|
+
font-weight: 500;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
.orchestration-status.draft { background: var(--bg-active); color: var(--text-secondary); }
|
|
2763
|
+
.orchestration-status.running { background: var(--status-progress-bg); color: var(--status-progress); }
|
|
2764
|
+
.orchestration-status.completed { background: var(--status-done-bg); color: var(--status-done); }
|
|
2765
|
+
.orchestration-status.failed { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
|
|
2766
|
+
.orchestration-status.paused { background: var(--status-pending-bg); color: var(--status-pending); }
|
|
2767
|
+
|
|
2768
|
+
.orchestration-agents {
|
|
2769
|
+
display: flex;
|
|
2770
|
+
gap: 4px;
|
|
2771
|
+
flex-wrap: wrap;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
.agent-badge {
|
|
2775
|
+
display: flex;
|
|
2776
|
+
align-items: center;
|
|
2777
|
+
gap: 4px;
|
|
2778
|
+
font-size: 11px;
|
|
2779
|
+
padding: 2px 6px;
|
|
2780
|
+
border-radius: 4px;
|
|
2781
|
+
background: var(--bg-active);
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
.agent-badge .status-dot {
|
|
2785
|
+
width: 6px;
|
|
2786
|
+
height: 6px;
|
|
2787
|
+
border-radius: 50%;
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
.agent-badge .status-dot.pending { background: var(--text-tertiary); }
|
|
2791
|
+
.agent-badge .status-dot.spawning { background: var(--status-pending); animation: pulse 1s infinite; }
|
|
2792
|
+
.agent-badge .status-dot.running { background: var(--status-progress); animation: pulse 1s infinite; }
|
|
2793
|
+
.agent-badge .status-dot.waiting { background: var(--status-pending); }
|
|
2794
|
+
.agent-badge .status-dot.completed { background: var(--status-done); }
|
|
2795
|
+
.agent-badge .status-dot.failed { background: #ef4444; }
|
|
2796
|
+
.agent-badge .status-dot.cancelled { background: var(--text-tertiary); }
|
|
2797
|
+
|
|
2798
|
+
@keyframes pulse {
|
|
2799
|
+
0%, 100% { opacity: 1; }
|
|
2800
|
+
50% { opacity: 0.5; }
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
.orchestration-detail {
|
|
2804
|
+
padding: 16px;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
.orchestration-detail-header {
|
|
2808
|
+
display: flex;
|
|
2809
|
+
align-items: center;
|
|
2810
|
+
justify-content: space-between;
|
|
2811
|
+
margin-bottom: 16px;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
.orchestration-detail-title {
|
|
2815
|
+
font-size: 18px;
|
|
2816
|
+
font-weight: 600;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
.orchestration-controls {
|
|
2820
|
+
display: flex;
|
|
2821
|
+
gap: 8px;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
.orch-btn {
|
|
2825
|
+
display: flex;
|
|
2826
|
+
align-items: center;
|
|
2827
|
+
gap: 6px;
|
|
2828
|
+
padding: 6px 12px;
|
|
2829
|
+
border: none;
|
|
2830
|
+
border-radius: 6px;
|
|
2831
|
+
font-size: 12px;
|
|
2832
|
+
font-weight: 500;
|
|
2833
|
+
cursor: pointer;
|
|
2834
|
+
transition: all 0.15s ease;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
.orch-btn.primary {
|
|
2838
|
+
background: var(--accent);
|
|
2839
|
+
color: white;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
.orch-btn.primary:hover {
|
|
2843
|
+
background: var(--accent-hover);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
.orch-btn.secondary {
|
|
2847
|
+
background: var(--bg-tertiary);
|
|
2848
|
+
color: var(--text-primary);
|
|
2849
|
+
border: 1px solid var(--border);
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
.orch-btn.secondary:hover {
|
|
2853
|
+
background: var(--bg-hover);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
.orch-btn.danger {
|
|
2857
|
+
background: rgba(239, 68, 68, 0.1);
|
|
2858
|
+
color: #ef4444;
|
|
2859
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
.orch-btn.danger:hover {
|
|
2863
|
+
background: rgba(239, 68, 68, 0.2);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
.agent-list {
|
|
2867
|
+
display: flex;
|
|
2868
|
+
flex-direction: column;
|
|
2869
|
+
gap: 8px;
|
|
2870
|
+
margin-top: 16px;
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
.agent-card {
|
|
2874
|
+
background: var(--bg-tertiary);
|
|
2875
|
+
border: 1px solid var(--border);
|
|
2876
|
+
border-radius: 8px;
|
|
2877
|
+
padding: 12px;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
.agent-card-header {
|
|
2881
|
+
display: flex;
|
|
2882
|
+
align-items: center;
|
|
2883
|
+
justify-content: space-between;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
.agent-info {
|
|
2887
|
+
display: flex;
|
|
2888
|
+
align-items: center;
|
|
2889
|
+
gap: 8px;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
.agent-status-indicator {
|
|
2893
|
+
width: 10px;
|
|
2894
|
+
height: 10px;
|
|
2895
|
+
border-radius: 50%;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
.agent-status-indicator.pending { background: var(--text-tertiary); }
|
|
2899
|
+
.agent-status-indicator.spawning { background: var(--status-pending); animation: pulse 1s infinite; }
|
|
2900
|
+
.agent-status-indicator.running { background: var(--status-progress); animation: pulse 1s infinite; }
|
|
2901
|
+
.agent-status-indicator.waiting { background: var(--status-pending); }
|
|
2902
|
+
.agent-status-indicator.completed { background: var(--status-done); }
|
|
2903
|
+
.agent-status-indicator.failed { background: #ef4444; }
|
|
2904
|
+
.agent-status-indicator.cancelled { background: var(--text-tertiary); }
|
|
2905
|
+
|
|
2906
|
+
.agent-name {
|
|
2907
|
+
font-weight: 500;
|
|
2908
|
+
font-size: 13px;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
.agent-model {
|
|
2912
|
+
font-size: 11px;
|
|
2913
|
+
color: var(--text-tertiary);
|
|
2914
|
+
padding: 1px 6px;
|
|
2915
|
+
background: var(--bg-active);
|
|
2916
|
+
border-radius: 4px;
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
.agent-actions {
|
|
2920
|
+
display: flex;
|
|
2921
|
+
gap: 4px;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
.agent-action-btn {
|
|
2925
|
+
padding: 4px 8px;
|
|
2926
|
+
border: none;
|
|
2927
|
+
border-radius: 4px;
|
|
2928
|
+
font-size: 11px;
|
|
2929
|
+
cursor: pointer;
|
|
2930
|
+
background: var(--bg-active);
|
|
2931
|
+
color: var(--text-secondary);
|
|
2932
|
+
transition: all 0.15s ease;
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
.agent-action-btn:hover {
|
|
2936
|
+
background: var(--bg-hover);
|
|
2937
|
+
color: var(--text-primary);
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
.agent-prompt {
|
|
2941
|
+
margin-top: 8px;
|
|
2942
|
+
font-size: 12px;
|
|
2943
|
+
color: var(--text-secondary);
|
|
2944
|
+
background: var(--bg-primary);
|
|
2945
|
+
padding: 8px;
|
|
2946
|
+
border-radius: 4px;
|
|
2947
|
+
white-space: pre-wrap;
|
|
2948
|
+
max-height: 80px;
|
|
2949
|
+
overflow: hidden;
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
.agent-dependencies {
|
|
2953
|
+
margin-top: 8px;
|
|
2954
|
+
font-size: 11px;
|
|
2955
|
+
color: var(--text-tertiary);
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
.create-orchestration-btn {
|
|
2959
|
+
width: 100%;
|
|
2960
|
+
padding: 10px;
|
|
2961
|
+
border: 1px dashed var(--border);
|
|
2962
|
+
border-radius: 8px;
|
|
2963
|
+
background: transparent;
|
|
2964
|
+
color: var(--text-secondary);
|
|
2965
|
+
font-size: 13px;
|
|
2966
|
+
cursor: pointer;
|
|
2967
|
+
transition: all 0.15s ease;
|
|
2968
|
+
display: flex;
|
|
2969
|
+
align-items: center;
|
|
2970
|
+
justify-content: center;
|
|
2971
|
+
gap: 6px;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
.create-orchestration-btn:hover {
|
|
2975
|
+
border-color: var(--accent);
|
|
2976
|
+
color: var(--accent);
|
|
2977
|
+
background: var(--bg-tertiary);
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
.orchestration-toggle-btn {
|
|
2981
|
+
display: flex;
|
|
2982
|
+
align-items: center;
|
|
2983
|
+
gap: 6px;
|
|
2984
|
+
padding: 6px 12px;
|
|
2985
|
+
border: 1px solid var(--border);
|
|
2986
|
+
border-radius: 6px;
|
|
2987
|
+
background: var(--bg-tertiary);
|
|
2988
|
+
color: var(--text-secondary);
|
|
2989
|
+
font-size: 12px;
|
|
2990
|
+
cursor: pointer;
|
|
2991
|
+
transition: all 0.15s ease;
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
.orchestration-toggle-btn:hover {
|
|
2995
|
+
background: var(--bg-hover);
|
|
2996
|
+
color: var(--text-primary);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
.orchestration-toggle-btn.active {
|
|
3000
|
+
background: var(--accent);
|
|
3001
|
+
color: white;
|
|
3002
|
+
border-color: var(--accent);
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
.add-agent-form {
|
|
3006
|
+
margin-top: 16px;
|
|
3007
|
+
padding: 12px;
|
|
3008
|
+
background: var(--bg-tertiary);
|
|
3009
|
+
border-radius: 8px;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
.add-agent-form .form-row {
|
|
3013
|
+
display: flex;
|
|
3014
|
+
gap: 8px;
|
|
3015
|
+
margin-bottom: 8px;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
.add-agent-form input,
|
|
3019
|
+
.add-agent-form select,
|
|
3020
|
+
.add-agent-form textarea {
|
|
3021
|
+
flex: 1;
|
|
3022
|
+
padding: 8px;
|
|
3023
|
+
border: 1px solid var(--border);
|
|
3024
|
+
border-radius: 4px;
|
|
3025
|
+
background: var(--bg-primary);
|
|
3026
|
+
color: var(--text-primary);
|
|
3027
|
+
font-size: 12px;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
.add-agent-form textarea {
|
|
3031
|
+
resize: vertical;
|
|
3032
|
+
min-height: 60px;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
.empty-orchestrations {
|
|
3036
|
+
text-align: center;
|
|
3037
|
+
padding: 40px 20px;
|
|
3038
|
+
color: var(--text-tertiary);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
.empty-orchestrations-icon {
|
|
3042
|
+
font-size: 48px;
|
|
3043
|
+
margin-bottom: 12px;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
/* Dependency Graph Styles */
|
|
3047
|
+
.dependency-graph {
|
|
3048
|
+
background: var(--bg-primary);
|
|
3049
|
+
border: 1px solid var(--border);
|
|
3050
|
+
border-radius: 8px;
|
|
3051
|
+
padding: 16px;
|
|
3052
|
+
margin-bottom: 16px;
|
|
3053
|
+
min-height: 150px;
|
|
3054
|
+
position: relative;
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
.dependency-graph-title {
|
|
3058
|
+
font-size: 11px;
|
|
3059
|
+
font-weight: 600;
|
|
3060
|
+
color: var(--text-tertiary);
|
|
3061
|
+
text-transform: uppercase;
|
|
3062
|
+
margin-bottom: 12px;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
.graph-container {
|
|
3066
|
+
position: relative;
|
|
3067
|
+
width: 100%;
|
|
3068
|
+
min-height: 120px;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
.graph-node {
|
|
3072
|
+
position: absolute;
|
|
3073
|
+
display: flex;
|
|
3074
|
+
flex-direction: column;
|
|
3075
|
+
align-items: center;
|
|
3076
|
+
gap: 4px;
|
|
3077
|
+
cursor: pointer;
|
|
3078
|
+
transition: transform 0.15s ease;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
.graph-node:hover {
|
|
3082
|
+
transform: scale(1.1);
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
.graph-node-circle {
|
|
3086
|
+
width: 36px;
|
|
3087
|
+
height: 36px;
|
|
3088
|
+
border-radius: 50%;
|
|
3089
|
+
display: flex;
|
|
3090
|
+
align-items: center;
|
|
3091
|
+
justify-content: center;
|
|
3092
|
+
font-size: 12px;
|
|
3093
|
+
font-weight: 600;
|
|
3094
|
+
color: white;
|
|
3095
|
+
border: 2px solid var(--bg-secondary);
|
|
3096
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
.graph-node-circle.pending { background: var(--text-tertiary); }
|
|
3100
|
+
.graph-node-circle.spawning { background: var(--status-pending); animation: pulse 1s infinite; }
|
|
3101
|
+
.graph-node-circle.running { background: var(--status-progress); animation: pulse 1s infinite; }
|
|
3102
|
+
.graph-node-circle.waiting { background: var(--status-pending); }
|
|
3103
|
+
.graph-node-circle.completed { background: var(--status-done); }
|
|
3104
|
+
.graph-node-circle.failed { background: #ef4444; }
|
|
3105
|
+
.graph-node-circle.cancelled { background: var(--text-tertiary); }
|
|
3106
|
+
|
|
3107
|
+
.graph-node-label {
|
|
3108
|
+
font-size: 10px;
|
|
3109
|
+
color: var(--text-secondary);
|
|
3110
|
+
max-width: 60px;
|
|
3111
|
+
text-align: center;
|
|
3112
|
+
white-space: nowrap;
|
|
3113
|
+
overflow: hidden;
|
|
3114
|
+
text-overflow: ellipsis;
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
.graph-svg {
|
|
3118
|
+
position: absolute;
|
|
3119
|
+
top: 0;
|
|
3120
|
+
left: 0;
|
|
3121
|
+
width: 100%;
|
|
3122
|
+
height: 100%;
|
|
3123
|
+
pointer-events: none;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
.graph-edge {
|
|
3127
|
+
stroke: var(--border);
|
|
3128
|
+
stroke-width: 2;
|
|
3129
|
+
fill: none;
|
|
3130
|
+
marker-end: url(#arrowhead);
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
.graph-edge.active {
|
|
3134
|
+
stroke: var(--status-progress);
|
|
3135
|
+
stroke-width: 2.5;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
/* Per-Agent Activity Feed */
|
|
3139
|
+
.agent-activity {
|
|
3140
|
+
margin-top: 8px;
|
|
3141
|
+
border-top: 1px solid var(--border);
|
|
3142
|
+
padding-top: 8px;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
.agent-activity-toggle {
|
|
3146
|
+
display: flex;
|
|
3147
|
+
align-items: center;
|
|
3148
|
+
gap: 6px;
|
|
3149
|
+
font-size: 11px;
|
|
3150
|
+
color: var(--text-tertiary);
|
|
3151
|
+
cursor: pointer;
|
|
3152
|
+
padding: 4px 0;
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
.agent-activity-toggle:hover {
|
|
3156
|
+
color: var(--text-secondary);
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
.agent-activity-toggle svg {
|
|
3160
|
+
transition: transform 0.15s ease;
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
.agent-activity-toggle.expanded svg {
|
|
3164
|
+
transform: rotate(90deg);
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
.agent-activity-feed {
|
|
3168
|
+
max-height: 0;
|
|
3169
|
+
overflow: hidden;
|
|
3170
|
+
transition: max-height 0.2s ease;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
.agent-activity-feed.expanded {
|
|
3174
|
+
max-height: 200px;
|
|
3175
|
+
overflow-y: auto;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
.agent-activity-item {
|
|
3179
|
+
display: flex;
|
|
3180
|
+
align-items: flex-start;
|
|
3181
|
+
gap: 8px;
|
|
3182
|
+
padding: 6px 0;
|
|
3183
|
+
font-size: 11px;
|
|
3184
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
.agent-activity-item:last-child {
|
|
3188
|
+
border-bottom: none;
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
.agent-activity-icon {
|
|
3192
|
+
width: 18px;
|
|
3193
|
+
height: 18px;
|
|
3194
|
+
border-radius: 4px;
|
|
3195
|
+
display: flex;
|
|
3196
|
+
align-items: center;
|
|
3197
|
+
justify-content: center;
|
|
3198
|
+
font-size: 10px;
|
|
3199
|
+
flex-shrink: 0;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
.agent-activity-icon.tool { background: var(--status-progress-bg); color: var(--status-progress); }
|
|
3203
|
+
.agent-activity-icon.success { background: var(--status-done-bg); color: var(--status-done); }
|
|
3204
|
+
.agent-activity-icon.error { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
|
|
3205
|
+
|
|
3206
|
+
.agent-activity-content {
|
|
3207
|
+
flex: 1;
|
|
3208
|
+
min-width: 0;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
.agent-activity-tool {
|
|
3212
|
+
font-weight: 500;
|
|
3213
|
+
color: var(--text-primary);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
.agent-activity-time {
|
|
3217
|
+
color: var(--text-tertiary);
|
|
3218
|
+
font-size: 10px;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
.agent-activity-empty {
|
|
3222
|
+
padding: 12px;
|
|
3223
|
+
text-align: center;
|
|
3224
|
+
color: var(--text-tertiary);
|
|
3225
|
+
font-size: 11px;
|
|
3226
|
+
}
|
|
2675
3227
|
</style>
|
|
2676
3228
|
</head>
|
|
2677
3229
|
<body>
|
|
@@ -2758,6 +3310,13 @@
|
|
|
2758
3310
|
</svg>
|
|
2759
3311
|
Conversation
|
|
2760
3312
|
</button>
|
|
3313
|
+
<button class="orchestration-toggle-btn" onclick="toggleOrchestrationPanel()" id="orch-toggle">
|
|
3314
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3315
|
+
<circle cx="12" cy="12" r="3"/>
|
|
3316
|
+
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
|
|
3317
|
+
</svg>
|
|
3318
|
+
Agents
|
|
3319
|
+
</button>
|
|
2761
3320
|
<button class="filter-btn" onclick="toggleTheme()" id="theme-toggle">
|
|
2762
3321
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="theme-icon-dark" style="display:none">
|
|
2763
3322
|
<circle cx="12" cy="12" r="5"/>
|
|
@@ -2880,6 +3439,93 @@
|
|
|
2880
3439
|
</div>
|
|
2881
3440
|
</div>
|
|
2882
3441
|
|
|
3442
|
+
<!-- Orchestration Panel -->
|
|
3443
|
+
<div class="orchestration-panel" id="orchestration-panel">
|
|
3444
|
+
<div class="orchestration-header">
|
|
3445
|
+
<h3>
|
|
3446
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3447
|
+
<circle cx="12" cy="12" r="3"/>
|
|
3448
|
+
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
|
|
3449
|
+
</svg>
|
|
3450
|
+
Multi-Agent Orchestration
|
|
3451
|
+
</h3>
|
|
3452
|
+
<button class="close-btn" onclick="toggleOrchestrationPanel()">×</button>
|
|
3453
|
+
</div>
|
|
3454
|
+
<div class="orchestration-content">
|
|
3455
|
+
<div id="orchestration-view">
|
|
3456
|
+
<!-- List view -->
|
|
3457
|
+
<div id="orchestration-list-view">
|
|
3458
|
+
<div class="orchestration-list" id="orchestration-list"></div>
|
|
3459
|
+
<button class="create-orchestration-btn" onclick="openCreateOrchestrationModal()">
|
|
3460
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3461
|
+
<path d="M12 5v14M5 12h14"/>
|
|
3462
|
+
</svg>
|
|
3463
|
+
New Orchestration
|
|
3464
|
+
</button>
|
|
3465
|
+
</div>
|
|
3466
|
+
|
|
3467
|
+
<!-- Detail view -->
|
|
3468
|
+
<div id="orchestration-detail-view" style="display: none;">
|
|
3469
|
+
<div class="orchestration-detail">
|
|
3470
|
+
<button class="orch-btn secondary" onclick="showOrchestrationList()" style="margin-bottom: 16px;">
|
|
3471
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3472
|
+
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
3473
|
+
</svg>
|
|
3474
|
+
Back
|
|
3475
|
+
</button>
|
|
3476
|
+
<div class="orchestration-detail-header">
|
|
3477
|
+
<div>
|
|
3478
|
+
<div class="orchestration-detail-title" id="orch-detail-name">Orchestration</div>
|
|
3479
|
+
<div class="orchestration-status" id="orch-detail-status">draft</div>
|
|
3480
|
+
</div>
|
|
3481
|
+
<div class="orchestration-controls" id="orch-controls"></div>
|
|
3482
|
+
</div>
|
|
3483
|
+
<div class="agent-list" id="agent-list"></div>
|
|
3484
|
+
<div class="add-agent-form" id="add-agent-form">
|
|
3485
|
+
<h4 style="margin-bottom: 8px; font-size: 12px;">Add Agent</h4>
|
|
3486
|
+
<div class="form-row">
|
|
3487
|
+
<input type="text" id="new-agent-name" placeholder="Agent name (e.g., Frontend Expert)">
|
|
3488
|
+
<select id="new-agent-model">
|
|
3489
|
+
<option value="sonnet">Sonnet</option>
|
|
3490
|
+
<option value="haiku">Haiku</option>
|
|
3491
|
+
<option value="opus">Opus</option>
|
|
3492
|
+
</select>
|
|
3493
|
+
</div>
|
|
3494
|
+
<textarea id="new-agent-prompt" placeholder="Initial prompt for this agent..."></textarea>
|
|
3495
|
+
<button class="orch-btn primary" onclick="addAgentToOrchestration()" style="margin-top: 8px; width: 100%;">
|
|
3496
|
+
Add Agent
|
|
3497
|
+
</button>
|
|
3498
|
+
</div>
|
|
3499
|
+
</div>
|
|
3500
|
+
</div>
|
|
3501
|
+
</div>
|
|
3502
|
+
</div>
|
|
3503
|
+
</div>
|
|
3504
|
+
|
|
3505
|
+
<!-- Create Orchestration Modal -->
|
|
3506
|
+
<div class="modal-overlay" id="create-orchestration-modal" onclick="closeCreateOrchestrationModal(event)">
|
|
3507
|
+
<div class="modal" onclick="event.stopPropagation()">
|
|
3508
|
+
<div class="modal-header">
|
|
3509
|
+
<h3>Create New Orchestration</h3>
|
|
3510
|
+
<button class="close-btn" onclick="closeCreateOrchestrationModal()">×</button>
|
|
3511
|
+
</div>
|
|
3512
|
+
<form class="modal-form" id="create-orchestration-form" onsubmit="submitCreateOrchestration(event)">
|
|
3513
|
+
<div class="form-group">
|
|
3514
|
+
<label for="orch-name">Name</label>
|
|
3515
|
+
<input type="text" id="orch-name" placeholder="e.g., Full-Stack Feature" required>
|
|
3516
|
+
</div>
|
|
3517
|
+
<div class="form-group">
|
|
3518
|
+
<label for="orch-description">Description</label>
|
|
3519
|
+
<textarea id="orch-description" placeholder="What will this orchestration do?" rows="2"></textarea>
|
|
3520
|
+
</div>
|
|
3521
|
+
<div class="modal-actions">
|
|
3522
|
+
<button type="button" class="modal-btn secondary" onclick="closeCreateOrchestrationModal()">Cancel</button>
|
|
3523
|
+
<button type="submit" class="modal-btn primary">Create</button>
|
|
3524
|
+
</div>
|
|
3525
|
+
</form>
|
|
3526
|
+
</div>
|
|
3527
|
+
</div>
|
|
3528
|
+
|
|
2883
3529
|
<div class="toast" id="toast">
|
|
2884
3530
|
<svg class="toast-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2885
3531
|
<path d="M20 6L9 17l-5-5"/>
|
|
@@ -4454,6 +5100,22 @@
|
|
|
4454
5100
|
renderManagedSessions();
|
|
4455
5101
|
}
|
|
4456
5102
|
break;
|
|
5103
|
+
// Orchestration events
|
|
5104
|
+
case 'orchestration_created':
|
|
5105
|
+
case 'orchestration_updated':
|
|
5106
|
+
case 'orchestration_started':
|
|
5107
|
+
case 'orchestration_completed':
|
|
5108
|
+
case 'orchestration_failed':
|
|
5109
|
+
case 'orchestration_paused':
|
|
5110
|
+
case 'orchestration_deleted':
|
|
5111
|
+
case 'agent_created':
|
|
5112
|
+
case 'agent_spawned':
|
|
5113
|
+
case 'agent_status_changed':
|
|
5114
|
+
case 'agent_completed':
|
|
5115
|
+
case 'agent_failed':
|
|
5116
|
+
case 'agent_killed':
|
|
5117
|
+
handleOrchestrationEvent(data);
|
|
5118
|
+
break;
|
|
4457
5119
|
case 'update':
|
|
4458
5120
|
default:
|
|
4459
5121
|
// Fallback to full reload for unknown event types
|
|
@@ -6760,6 +7422,584 @@
|
|
|
6760
7422
|
console.error('Failed to load Claude events:', e);
|
|
6761
7423
|
}
|
|
6762
7424
|
}
|
|
7425
|
+
|
|
7426
|
+
// =========================================================================
|
|
7427
|
+
// Orchestration Management
|
|
7428
|
+
// =========================================================================
|
|
7429
|
+
|
|
7430
|
+
let orchestrations = [];
|
|
7431
|
+
let selectedOrchestration = null;
|
|
7432
|
+
let orchestrationPanelOpen = false;
|
|
7433
|
+
|
|
7434
|
+
// Toggle orchestration panel
|
|
7435
|
+
function toggleOrchestrationPanel() {
|
|
7436
|
+
orchestrationPanelOpen = !orchestrationPanelOpen;
|
|
7437
|
+
const panel = document.getElementById('orchestration-panel');
|
|
7438
|
+
const btn = document.getElementById('orch-toggle');
|
|
7439
|
+
|
|
7440
|
+
panel.classList.toggle('open', orchestrationPanelOpen);
|
|
7441
|
+
btn.classList.toggle('active', orchestrationPanelOpen);
|
|
7442
|
+
|
|
7443
|
+
if (orchestrationPanelOpen) {
|
|
7444
|
+
loadOrchestrations();
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
|
|
7448
|
+
// Load all orchestrations
|
|
7449
|
+
async function loadOrchestrations() {
|
|
7450
|
+
try {
|
|
7451
|
+
const res = await fetch('/api/orchestrations');
|
|
7452
|
+
const data = await res.json();
|
|
7453
|
+
if (data.ok) {
|
|
7454
|
+
orchestrations = data.orchestrations;
|
|
7455
|
+
renderOrchestrations();
|
|
7456
|
+
}
|
|
7457
|
+
} catch (e) {
|
|
7458
|
+
console.error('Failed to load orchestrations:', e);
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
|
|
7462
|
+
// Render orchestrations list
|
|
7463
|
+
function renderOrchestrations() {
|
|
7464
|
+
const list = document.getElementById('orchestration-list');
|
|
7465
|
+
|
|
7466
|
+
if (orchestrations.length === 0) {
|
|
7467
|
+
list.innerHTML = `
|
|
7468
|
+
<div class="empty-orchestrations">
|
|
7469
|
+
<div class="empty-orchestrations-icon">🤖</div>
|
|
7470
|
+
<h3>No Orchestrations</h3>
|
|
7471
|
+
<p>Create an orchestration to coordinate multiple Claude agents</p>
|
|
7472
|
+
</div>
|
|
7473
|
+
`;
|
|
7474
|
+
return;
|
|
7475
|
+
}
|
|
7476
|
+
|
|
7477
|
+
list.innerHTML = orchestrations.map(orch => `
|
|
7478
|
+
<div class="orchestration-card ${selectedOrchestration?.id === orch.id ? 'active' : ''}"
|
|
7479
|
+
onclick="selectOrchestration('${orch.id}')">
|
|
7480
|
+
<div class="orchestration-card-header">
|
|
7481
|
+
<span class="orchestration-name">${escapeHtml(orch.name)}</span>
|
|
7482
|
+
<span class="orchestration-status ${orch.status}">${orch.status}</span>
|
|
7483
|
+
</div>
|
|
7484
|
+
<div class="orchestration-agents">
|
|
7485
|
+
${orch.agents.map(agent => `
|
|
7486
|
+
<span class="agent-badge">
|
|
7487
|
+
<span class="status-dot ${agent.status}"></span>
|
|
7488
|
+
${escapeHtml(agent.name)}
|
|
7489
|
+
</span>
|
|
7490
|
+
`).join('')}
|
|
7491
|
+
${orch.agents.length === 0 ? '<span style="color: var(--text-tertiary); font-size: 11px;">No agents yet</span>' : ''}
|
|
7492
|
+
</div>
|
|
7493
|
+
</div>
|
|
7494
|
+
`).join('');
|
|
7495
|
+
}
|
|
7496
|
+
|
|
7497
|
+
// Select an orchestration and show detail view
|
|
7498
|
+
async function selectOrchestration(id) {
|
|
7499
|
+
try {
|
|
7500
|
+
const res = await fetch(`/api/orchestrations/${id}`);
|
|
7501
|
+
const data = await res.json();
|
|
7502
|
+
if (data.ok) {
|
|
7503
|
+
selectedOrchestration = data.orchestration;
|
|
7504
|
+
showOrchestrationDetail();
|
|
7505
|
+
}
|
|
7506
|
+
} catch (e) {
|
|
7507
|
+
console.error('Failed to load orchestration:', e);
|
|
7508
|
+
}
|
|
7509
|
+
}
|
|
7510
|
+
|
|
7511
|
+
// Show orchestration detail view
|
|
7512
|
+
function showOrchestrationDetail() {
|
|
7513
|
+
document.getElementById('orchestration-list-view').style.display = 'none';
|
|
7514
|
+
document.getElementById('orchestration-detail-view').style.display = 'block';
|
|
7515
|
+
renderOrchestrationDetail();
|
|
7516
|
+
}
|
|
7517
|
+
|
|
7518
|
+
// Show orchestration list view
|
|
7519
|
+
function showOrchestrationList() {
|
|
7520
|
+
document.getElementById('orchestration-list-view').style.display = 'block';
|
|
7521
|
+
document.getElementById('orchestration-detail-view').style.display = 'none';
|
|
7522
|
+
selectedOrchestration = null;
|
|
7523
|
+
loadOrchestrations();
|
|
7524
|
+
}
|
|
7525
|
+
|
|
7526
|
+
// Render orchestration detail
|
|
7527
|
+
function renderOrchestrationDetail() {
|
|
7528
|
+
if (!selectedOrchestration) return;
|
|
7529
|
+
|
|
7530
|
+
const orch = selectedOrchestration;
|
|
7531
|
+
document.getElementById('orch-detail-name').textContent = orch.name;
|
|
7532
|
+
|
|
7533
|
+
const statusEl = document.getElementById('orch-detail-status');
|
|
7534
|
+
statusEl.textContent = orch.status;
|
|
7535
|
+
statusEl.className = `orchestration-status ${orch.status}`;
|
|
7536
|
+
|
|
7537
|
+
// Render controls based on status
|
|
7538
|
+
const controls = document.getElementById('orch-controls');
|
|
7539
|
+
if (orch.status === 'draft' || orch.status === 'paused') {
|
|
7540
|
+
controls.innerHTML = `
|
|
7541
|
+
<button class="orch-btn primary" onclick="startOrchestration('${orch.id}')">
|
|
7542
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
7543
|
+
<polygon points="5 3 19 12 5 21 5 3"/>
|
|
7544
|
+
</svg>
|
|
7545
|
+
Start
|
|
7546
|
+
</button>
|
|
7547
|
+
<button class="orch-btn danger" onclick="deleteOrchestration('${orch.id}')">Delete</button>
|
|
7548
|
+
`;
|
|
7549
|
+
} else if (orch.status === 'running') {
|
|
7550
|
+
controls.innerHTML = `
|
|
7551
|
+
<button class="orch-btn secondary" onclick="stopOrchestration('${orch.id}')">
|
|
7552
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
7553
|
+
<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>
|
|
7554
|
+
</svg>
|
|
7555
|
+
Stop
|
|
7556
|
+
</button>
|
|
7557
|
+
`;
|
|
7558
|
+
} else {
|
|
7559
|
+
controls.innerHTML = `
|
|
7560
|
+
<button class="orch-btn danger" onclick="deleteOrchestration('${orch.id}')">Delete</button>
|
|
7561
|
+
`;
|
|
7562
|
+
}
|
|
7563
|
+
|
|
7564
|
+
// Render dependency graph if there are agents
|
|
7565
|
+
const agentList = document.getElementById('agent-list');
|
|
7566
|
+
let graphHtml = '';
|
|
7567
|
+
if (orch.agents.length > 1) {
|
|
7568
|
+
graphHtml = renderDependencyGraph(orch);
|
|
7569
|
+
}
|
|
7570
|
+
|
|
7571
|
+
// Render agents
|
|
7572
|
+
if (orch.agents.length === 0) {
|
|
7573
|
+
agentList.innerHTML = '<p style="color: var(--text-tertiary); text-align: center; padding: 20px;">No agents yet. Add one below.</p>';
|
|
7574
|
+
} else {
|
|
7575
|
+
agentList.innerHTML = graphHtml + orch.agents.map(agent => `
|
|
7576
|
+
<div class="agent-card" id="agent-card-${agent.id}">
|
|
7577
|
+
<div class="agent-card-header">
|
|
7578
|
+
<div class="agent-info">
|
|
7579
|
+
<span class="agent-status-indicator ${agent.status}"></span>
|
|
7580
|
+
<span class="agent-name">${escapeHtml(agent.name)}</span>
|
|
7581
|
+
<span class="agent-model">${agent.model}</span>
|
|
7582
|
+
</div>
|
|
7583
|
+
<div class="agent-actions">
|
|
7584
|
+
${agent.status === 'pending' && orch.status === 'running' ?
|
|
7585
|
+
`<button class="agent-action-btn" onclick="spawnAgent('${orch.id}', '${agent.id}')">Spawn</button>` : ''}
|
|
7586
|
+
${agent.status === 'running' ?
|
|
7587
|
+
`<button class="agent-action-btn" onclick="killAgent('${orch.id}', '${agent.id}')">Kill</button>` : ''}
|
|
7588
|
+
${['pending', 'completed', 'failed', 'cancelled'].includes(agent.status) ?
|
|
7589
|
+
`<button class="agent-action-btn" onclick="removeAgent('${orch.id}', '${agent.id}')">Remove</button>` : ''}
|
|
7590
|
+
</div>
|
|
7591
|
+
</div>
|
|
7592
|
+
${agent.prompt ? `<div class="agent-prompt">${escapeHtml(agent.prompt.substring(0, 200))}${agent.prompt.length > 200 ? '...' : ''}</div>` : ''}
|
|
7593
|
+
${agent.dependencies.length > 0 ? `
|
|
7594
|
+
<div class="agent-dependencies">
|
|
7595
|
+
Depends on: ${agent.dependencies.map(depId => {
|
|
7596
|
+
const dep = orch.agents.find(a => a.id === depId);
|
|
7597
|
+
return dep ? escapeHtml(dep.name) : depId.slice(-8);
|
|
7598
|
+
}).join(', ')}
|
|
7599
|
+
</div>
|
|
7600
|
+
` : ''}
|
|
7601
|
+
${agent.error ? `<div style="color: #ef4444; font-size: 11px; margin-top: 4px;">Error: ${escapeHtml(agent.error)}</div>` : ''}
|
|
7602
|
+
${renderAgentActivityFeed(agent)}
|
|
7603
|
+
</div>
|
|
7604
|
+
`).join('');
|
|
7605
|
+
}
|
|
7606
|
+
|
|
7607
|
+
// Show/hide add agent form based on status
|
|
7608
|
+
const addForm = document.getElementById('add-agent-form');
|
|
7609
|
+
addForm.style.display = orch.status === 'completed' ? 'none' : 'block';
|
|
7610
|
+
}
|
|
7611
|
+
|
|
7612
|
+
// Render dependency graph visualization
|
|
7613
|
+
function renderDependencyGraph(orch) {
|
|
7614
|
+
if (orch.agents.length < 2) return '';
|
|
7615
|
+
|
|
7616
|
+
const agents = orch.agents;
|
|
7617
|
+
const nodeWidth = 60;
|
|
7618
|
+
const nodeHeight = 60;
|
|
7619
|
+
const padding = 20;
|
|
7620
|
+
|
|
7621
|
+
// Calculate positions using a simple layout algorithm
|
|
7622
|
+
// Agents with no dependencies go first (left), then their dependents
|
|
7623
|
+
const levels = [];
|
|
7624
|
+
const agentLevels = new Map();
|
|
7625
|
+
|
|
7626
|
+
// Find agents with no dependencies (root level)
|
|
7627
|
+
const roots = agents.filter(a => a.dependencies.length === 0);
|
|
7628
|
+
if (roots.length === 0) {
|
|
7629
|
+
// Circular dependency or all have deps, just line them up
|
|
7630
|
+
agents.forEach((a, i) => agentLevels.set(a.id, 0));
|
|
7631
|
+
} else {
|
|
7632
|
+
// BFS to assign levels
|
|
7633
|
+
const queue = roots.map(a => ({ agent: a, level: 0 }));
|
|
7634
|
+
const visited = new Set();
|
|
7635
|
+
|
|
7636
|
+
while (queue.length > 0) {
|
|
7637
|
+
const { agent, level } = queue.shift();
|
|
7638
|
+
if (visited.has(agent.id)) continue;
|
|
7639
|
+
visited.add(agent.id);
|
|
7640
|
+
agentLevels.set(agent.id, level);
|
|
7641
|
+
|
|
7642
|
+
// Find agents that depend on this one
|
|
7643
|
+
const dependents = agents.filter(a => a.dependencies.includes(agent.id));
|
|
7644
|
+
dependents.forEach(d => {
|
|
7645
|
+
if (!visited.has(d.id)) {
|
|
7646
|
+
queue.push({ agent: d, level: level + 1 });
|
|
7647
|
+
}
|
|
7648
|
+
});
|
|
7649
|
+
}
|
|
7650
|
+
|
|
7651
|
+
// Handle any unvisited (disconnected) agents
|
|
7652
|
+
agents.forEach(a => {
|
|
7653
|
+
if (!agentLevels.has(a.id)) {
|
|
7654
|
+
agentLevels.set(a.id, 0);
|
|
7655
|
+
}
|
|
7656
|
+
});
|
|
7657
|
+
}
|
|
7658
|
+
|
|
7659
|
+
// Group agents by level
|
|
7660
|
+
const levelGroups = new Map();
|
|
7661
|
+
agents.forEach(a => {
|
|
7662
|
+
const level = agentLevels.get(a.id);
|
|
7663
|
+
if (!levelGroups.has(level)) levelGroups.set(level, []);
|
|
7664
|
+
levelGroups.get(level).push(a);
|
|
7665
|
+
});
|
|
7666
|
+
|
|
7667
|
+
const maxLevel = Math.max(...agentLevels.values());
|
|
7668
|
+
const graphWidth = (maxLevel + 1) * 100;
|
|
7669
|
+
const maxNodesInLevel = Math.max(...[...levelGroups.values()].map(g => g.length));
|
|
7670
|
+
const graphHeight = Math.max(120, maxNodesInLevel * 50 + 40);
|
|
7671
|
+
|
|
7672
|
+
// Calculate node positions
|
|
7673
|
+
const positions = new Map();
|
|
7674
|
+
for (const [level, levelAgents] of levelGroups) {
|
|
7675
|
+
const x = padding + level * 100 + nodeWidth / 2;
|
|
7676
|
+
levelAgents.forEach((agent, idx) => {
|
|
7677
|
+
const y = padding + (idx * 50) + (graphHeight - levelAgents.length * 50) / 2;
|
|
7678
|
+
positions.set(agent.id, { x, y });
|
|
7679
|
+
});
|
|
7680
|
+
}
|
|
7681
|
+
|
|
7682
|
+
// Generate SVG edges
|
|
7683
|
+
let edges = '';
|
|
7684
|
+
agents.forEach(agent => {
|
|
7685
|
+
const toPos = positions.get(agent.id);
|
|
7686
|
+
agent.dependencies.forEach(depId => {
|
|
7687
|
+
const fromPos = positions.get(depId);
|
|
7688
|
+
if (fromPos && toPos) {
|
|
7689
|
+
const depAgent = agents.find(a => a.id === depId);
|
|
7690
|
+
const isActive = depAgent && depAgent.status === 'running';
|
|
7691
|
+
edges += `<path class="graph-edge ${isActive ? 'active' : ''}" d="M${fromPos.x + 18} ${fromPos.y + 18} C${fromPos.x + 50} ${fromPos.y + 18}, ${toPos.x - 32} ${toPos.y + 18}, ${toPos.x - 18} ${toPos.y + 18}"/>`;
|
|
7692
|
+
}
|
|
7693
|
+
});
|
|
7694
|
+
});
|
|
7695
|
+
|
|
7696
|
+
// Generate nodes
|
|
7697
|
+
let nodes = '';
|
|
7698
|
+
agents.forEach((agent, idx) => {
|
|
7699
|
+
const pos = positions.get(agent.id);
|
|
7700
|
+
const initial = agent.name.charAt(0).toUpperCase();
|
|
7701
|
+
nodes += `
|
|
7702
|
+
<div class="graph-node" style="left: ${pos.x - 18}px; top: ${pos.y}px;" onclick="scrollToAgent('${agent.id}')" title="${escapeHtml(agent.name)} (${agent.status})">
|
|
7703
|
+
<div class="graph-node-circle ${agent.status}">${initial}</div>
|
|
7704
|
+
<div class="graph-node-label">${escapeHtml(agent.name)}</div>
|
|
7705
|
+
</div>
|
|
7706
|
+
`;
|
|
7707
|
+
});
|
|
7708
|
+
|
|
7709
|
+
return `
|
|
7710
|
+
<div class="dependency-graph">
|
|
7711
|
+
<div class="dependency-graph-title">Execution Flow</div>
|
|
7712
|
+
<div class="graph-container" style="height: ${graphHeight}px;">
|
|
7713
|
+
<svg class="graph-svg" style="width: ${graphWidth + padding * 2}px; height: ${graphHeight}px;">
|
|
7714
|
+
<defs>
|
|
7715
|
+
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
7716
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="var(--border)"/>
|
|
7717
|
+
</marker>
|
|
7718
|
+
</defs>
|
|
7719
|
+
${edges}
|
|
7720
|
+
</svg>
|
|
7721
|
+
${nodes}
|
|
7722
|
+
</div>
|
|
7723
|
+
</div>
|
|
7724
|
+
`;
|
|
7725
|
+
}
|
|
7726
|
+
|
|
7727
|
+
// Scroll to agent card
|
|
7728
|
+
function scrollToAgent(agentId) {
|
|
7729
|
+
const card = document.getElementById(`agent-card-${agentId}`);
|
|
7730
|
+
if (card) {
|
|
7731
|
+
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7732
|
+
card.style.boxShadow = '0 0 0 2px var(--accent)';
|
|
7733
|
+
setTimeout(() => { card.style.boxShadow = ''; }, 2000);
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
|
|
7737
|
+
// Render per-agent activity feed
|
|
7738
|
+
function renderAgentActivityFeed(agent) {
|
|
7739
|
+
// Only show activity for agents that have been spawned
|
|
7740
|
+
if (!agent.sessionId && agent.status === 'pending') {
|
|
7741
|
+
return '';
|
|
7742
|
+
}
|
|
7743
|
+
|
|
7744
|
+
// Filter claude events for this agent's session
|
|
7745
|
+
const agentEvents = agent.sessionId
|
|
7746
|
+
? claudeEvents.filter(e => e.sessionId === agent.sessionId).slice(-10)
|
|
7747
|
+
: [];
|
|
7748
|
+
|
|
7749
|
+
// Also include tool usage from agent data
|
|
7750
|
+
const toolEvents = (agent.toolsUsed || []).slice(-5);
|
|
7751
|
+
|
|
7752
|
+
const hasActivity = agentEvents.length > 0 || toolEvents.length > 0;
|
|
7753
|
+
|
|
7754
|
+
return `
|
|
7755
|
+
<div class="agent-activity">
|
|
7756
|
+
<div class="agent-activity-toggle" onclick="toggleAgentActivity(this)">
|
|
7757
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
7758
|
+
<path d="M9 18l6-6-6-6"/>
|
|
7759
|
+
</svg>
|
|
7760
|
+
Activity (${agentEvents.length + toolEvents.length})
|
|
7761
|
+
</div>
|
|
7762
|
+
<div class="agent-activity-feed">
|
|
7763
|
+
${!hasActivity ? '<div class="agent-activity-empty">No activity recorded yet</div>' : ''}
|
|
7764
|
+
${toolEvents.map(t => `
|
|
7765
|
+
<div class="agent-activity-item">
|
|
7766
|
+
<span class="agent-activity-icon ${t.status === 'success' ? 'success' : t.status === 'error' ? 'error' : 'tool'}">
|
|
7767
|
+
${t.status === 'success' ? '✓' : t.status === 'error' ? '!' : '⚡'}
|
|
7768
|
+
</span>
|
|
7769
|
+
<div class="agent-activity-content">
|
|
7770
|
+
<span class="agent-activity-tool">${escapeHtml(t.tool)}</span>
|
|
7771
|
+
${t.duration ? `<span class="agent-activity-time">${t.duration}ms</span>` : ''}
|
|
7772
|
+
</div>
|
|
7773
|
+
</div>
|
|
7774
|
+
`).join('')}
|
|
7775
|
+
${agentEvents.map(e => `
|
|
7776
|
+
<div class="agent-activity-item">
|
|
7777
|
+
<span class="agent-activity-icon tool">⚡</span>
|
|
7778
|
+
<div class="agent-activity-content">
|
|
7779
|
+
<span class="agent-activity-tool">${escapeHtml(e.tool || e.type)}</span>
|
|
7780
|
+
<span class="agent-activity-time">${new Date(e.timestamp).toLocaleTimeString()}</span>
|
|
7781
|
+
</div>
|
|
7782
|
+
</div>
|
|
7783
|
+
`).join('')}
|
|
7784
|
+
</div>
|
|
7785
|
+
</div>
|
|
7786
|
+
`;
|
|
7787
|
+
}
|
|
7788
|
+
|
|
7789
|
+
// Toggle agent activity feed expansion
|
|
7790
|
+
function toggleAgentActivity(el) {
|
|
7791
|
+
el.classList.toggle('expanded');
|
|
7792
|
+
const feed = el.nextElementSibling;
|
|
7793
|
+
feed.classList.toggle('expanded');
|
|
7794
|
+
}
|
|
7795
|
+
|
|
7796
|
+
// Create orchestration modal
|
|
7797
|
+
function openCreateOrchestrationModal() {
|
|
7798
|
+
document.getElementById('create-orchestration-modal').style.display = 'flex';
|
|
7799
|
+
document.getElementById('orch-name').focus();
|
|
7800
|
+
}
|
|
7801
|
+
|
|
7802
|
+
function closeCreateOrchestrationModal(event) {
|
|
7803
|
+
if (event && event.target !== event.currentTarget) return;
|
|
7804
|
+
document.getElementById('create-orchestration-modal').style.display = 'none';
|
|
7805
|
+
document.getElementById('create-orchestration-form').reset();
|
|
7806
|
+
}
|
|
7807
|
+
|
|
7808
|
+
async function submitCreateOrchestration(event) {
|
|
7809
|
+
event.preventDefault();
|
|
7810
|
+
|
|
7811
|
+
const name = document.getElementById('orch-name').value.trim();
|
|
7812
|
+
const description = document.getElementById('orch-description').value.trim();
|
|
7813
|
+
|
|
7814
|
+
try {
|
|
7815
|
+
const res = await fetch('/api/orchestrations', {
|
|
7816
|
+
method: 'POST',
|
|
7817
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7818
|
+
body: JSON.stringify({ name, description })
|
|
7819
|
+
});
|
|
7820
|
+
const data = await res.json();
|
|
7821
|
+
if (data.ok) {
|
|
7822
|
+
closeCreateOrchestrationModal();
|
|
7823
|
+
showToast('Orchestration created');
|
|
7824
|
+
loadOrchestrations();
|
|
7825
|
+
selectOrchestration(data.orchestration.id);
|
|
7826
|
+
} else {
|
|
7827
|
+
alert(data.error || 'Failed to create orchestration');
|
|
7828
|
+
}
|
|
7829
|
+
} catch (e) {
|
|
7830
|
+
console.error('Failed to create orchestration:', e);
|
|
7831
|
+
alert('Failed to create orchestration');
|
|
7832
|
+
}
|
|
7833
|
+
}
|
|
7834
|
+
|
|
7835
|
+
// Add agent to orchestration
|
|
7836
|
+
async function addAgentToOrchestration() {
|
|
7837
|
+
if (!selectedOrchestration) return;
|
|
7838
|
+
|
|
7839
|
+
const name = document.getElementById('new-agent-name').value.trim();
|
|
7840
|
+
const model = document.getElementById('new-agent-model').value;
|
|
7841
|
+
const prompt = document.getElementById('new-agent-prompt').value.trim();
|
|
7842
|
+
|
|
7843
|
+
if (!name) {
|
|
7844
|
+
alert('Please enter an agent name');
|
|
7845
|
+
return;
|
|
7846
|
+
}
|
|
7847
|
+
|
|
7848
|
+
try {
|
|
7849
|
+
const res = await fetch(`/api/orchestrations/${selectedOrchestration.id}/agents`, {
|
|
7850
|
+
method: 'POST',
|
|
7851
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7852
|
+
body: JSON.stringify({ name, model, prompt, workingDirectory: '.' })
|
|
7853
|
+
});
|
|
7854
|
+
const data = await res.json();
|
|
7855
|
+
if (data.ok) {
|
|
7856
|
+
// Clear form
|
|
7857
|
+
document.getElementById('new-agent-name').value = '';
|
|
7858
|
+
document.getElementById('new-agent-prompt').value = '';
|
|
7859
|
+
showToast('Agent added');
|
|
7860
|
+
selectOrchestration(selectedOrchestration.id);
|
|
7861
|
+
} else {
|
|
7862
|
+
alert(data.error || 'Failed to add agent');
|
|
7863
|
+
}
|
|
7864
|
+
} catch (e) {
|
|
7865
|
+
console.error('Failed to add agent:', e);
|
|
7866
|
+
alert('Failed to add agent');
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
|
|
7870
|
+
// Start orchestration
|
|
7871
|
+
async function startOrchestration(id) {
|
|
7872
|
+
try {
|
|
7873
|
+
const res = await fetch(`/api/orchestrations/${id}/start`, { method: 'POST' });
|
|
7874
|
+
const data = await res.json();
|
|
7875
|
+
if (data.ok) {
|
|
7876
|
+
showToast(`Started with ${data.spawnedAgents} agents`);
|
|
7877
|
+
selectOrchestration(id);
|
|
7878
|
+
} else {
|
|
7879
|
+
alert(data.error || 'Failed to start orchestration');
|
|
7880
|
+
}
|
|
7881
|
+
} catch (e) {
|
|
7882
|
+
console.error('Failed to start orchestration:', e);
|
|
7883
|
+
alert('Failed to start orchestration');
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
|
|
7887
|
+
// Stop orchestration
|
|
7888
|
+
async function stopOrchestration(id) {
|
|
7889
|
+
try {
|
|
7890
|
+
const res = await fetch(`/api/orchestrations/${id}/stop`, { method: 'POST' });
|
|
7891
|
+
const data = await res.json();
|
|
7892
|
+
if (data.ok) {
|
|
7893
|
+
showToast('Orchestration stopped');
|
|
7894
|
+
selectOrchestration(id);
|
|
7895
|
+
} else {
|
|
7896
|
+
alert(data.error || 'Failed to stop orchestration');
|
|
7897
|
+
}
|
|
7898
|
+
} catch (e) {
|
|
7899
|
+
console.error('Failed to stop orchestration:', e);
|
|
7900
|
+
alert('Failed to stop orchestration');
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
|
|
7904
|
+
// Delete orchestration
|
|
7905
|
+
async function deleteOrchestration(id) {
|
|
7906
|
+
if (!confirm('Are you sure you want to delete this orchestration?')) return;
|
|
7907
|
+
|
|
7908
|
+
try {
|
|
7909
|
+
const res = await fetch(`/api/orchestrations/${id}`, { method: 'DELETE' });
|
|
7910
|
+
const data = await res.json();
|
|
7911
|
+
if (data.ok) {
|
|
7912
|
+
showToast('Orchestration deleted');
|
|
7913
|
+
showOrchestrationList();
|
|
7914
|
+
} else {
|
|
7915
|
+
alert(data.error || 'Failed to delete orchestration');
|
|
7916
|
+
}
|
|
7917
|
+
} catch (e) {
|
|
7918
|
+
console.error('Failed to delete orchestration:', e);
|
|
7919
|
+
alert('Failed to delete orchestration');
|
|
7920
|
+
}
|
|
7921
|
+
}
|
|
7922
|
+
|
|
7923
|
+
// Spawn individual agent
|
|
7924
|
+
async function spawnAgent(orchId, agentId) {
|
|
7925
|
+
try {
|
|
7926
|
+
const res = await fetch(`/api/orchestrations/${orchId}/agents/${agentId}/spawn`, { method: 'POST' });
|
|
7927
|
+
const data = await res.json();
|
|
7928
|
+
if (data.ok) {
|
|
7929
|
+
showToast('Agent spawned');
|
|
7930
|
+
selectOrchestration(orchId);
|
|
7931
|
+
} else {
|
|
7932
|
+
alert(data.error || 'Failed to spawn agent');
|
|
7933
|
+
}
|
|
7934
|
+
} catch (e) {
|
|
7935
|
+
console.error('Failed to spawn agent:', e);
|
|
7936
|
+
alert('Failed to spawn agent');
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
|
|
7940
|
+
// Kill individual agent
|
|
7941
|
+
async function killAgent(orchId, agentId) {
|
|
7942
|
+
try {
|
|
7943
|
+
const res = await fetch(`/api/orchestrations/${orchId}/agents/${agentId}/kill`, { method: 'POST' });
|
|
7944
|
+
const data = await res.json();
|
|
7945
|
+
if (data.ok) {
|
|
7946
|
+
showToast('Agent killed');
|
|
7947
|
+
selectOrchestration(orchId);
|
|
7948
|
+
} else {
|
|
7949
|
+
alert(data.error || 'Failed to kill agent');
|
|
7950
|
+
}
|
|
7951
|
+
} catch (e) {
|
|
7952
|
+
console.error('Failed to kill agent:', e);
|
|
7953
|
+
alert('Failed to kill agent');
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
|
|
7957
|
+
// Remove agent
|
|
7958
|
+
async function removeAgent(orchId, agentId) {
|
|
7959
|
+
try {
|
|
7960
|
+
const res = await fetch(`/api/orchestrations/${orchId}/agents/${agentId}`, { method: 'DELETE' });
|
|
7961
|
+
const data = await res.json();
|
|
7962
|
+
if (data.ok) {
|
|
7963
|
+
showToast('Agent removed');
|
|
7964
|
+
selectOrchestration(orchId);
|
|
7965
|
+
} else {
|
|
7966
|
+
alert(data.error || 'Failed to remove agent');
|
|
7967
|
+
}
|
|
7968
|
+
} catch (e) {
|
|
7969
|
+
console.error('Failed to remove agent:', e);
|
|
7970
|
+
alert('Failed to remove agent');
|
|
7971
|
+
}
|
|
7972
|
+
}
|
|
7973
|
+
|
|
7974
|
+
// Handle orchestration SSE events
|
|
7975
|
+
function handleOrchestrationEvent(event) {
|
|
7976
|
+
if (!orchestrationPanelOpen) return;
|
|
7977
|
+
|
|
7978
|
+
const eventTypes = [
|
|
7979
|
+
'orchestration_created', 'orchestration_updated', 'orchestration_started',
|
|
7980
|
+
'orchestration_completed', 'orchestration_failed', 'orchestration_paused',
|
|
7981
|
+
'orchestration_deleted', 'agent_created', 'agent_spawned', 'agent_status_changed',
|
|
7982
|
+
'agent_completed', 'agent_failed', 'agent_killed'
|
|
7983
|
+
];
|
|
7984
|
+
|
|
7985
|
+
if (eventTypes.includes(event.type)) {
|
|
7986
|
+
// Refresh orchestration list or detail
|
|
7987
|
+
if (selectedOrchestration && event.orchestrationId === selectedOrchestration.id) {
|
|
7988
|
+
selectOrchestration(selectedOrchestration.id);
|
|
7989
|
+
} else {
|
|
7990
|
+
loadOrchestrations();
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
|
|
7995
|
+
// Hook into SSE events
|
|
7996
|
+
const originalHandleSSEMessage = typeof handleSSEMessage !== 'undefined' ? handleSSEMessage : null;
|
|
7997
|
+
function handleSSEMessageWithOrchestration(event) {
|
|
7998
|
+
if (originalHandleSSEMessage) {
|
|
7999
|
+
originalHandleSSEMessage(event);
|
|
8000
|
+
}
|
|
8001
|
+
handleOrchestrationEvent(event);
|
|
8002
|
+
}
|
|
6763
8003
|
</script>
|
|
6764
8004
|
</body>
|
|
6765
8005
|
</html>
|