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/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()">&times;</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()">&times;</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>