claudehq 1.0.3 → 1.0.5
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/claude-events.js +2 -1
- package/lib/core/config.js +31 -17
- package/lib/core/event-bus.js +0 -18
- package/lib/index.js +181 -74
- package/lib/routes/api.js +0 -399
- package/lib/routes/orchestration.js +417 -0
- package/lib/routes/spawner.js +335 -0
- package/lib/sessions/manager.js +36 -9
- package/lib/spawner/index.js +51 -0
- package/lib/spawner/path-validator.js +366 -0
- package/lib/spawner/projects-manager.js +421 -0
- package/lib/spawner/session-spawner.js +1010 -0
- package/package.json +1 -1
- package/public/index.html +512 -1371
package/public/index.html
CHANGED
|
@@ -447,6 +447,70 @@
|
|
|
447
447
|
color: #ef4444;
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
/* Offline Sessions Section */
|
|
451
|
+
.offline-sessions-section {
|
|
452
|
+
margin-top: 8px;
|
|
453
|
+
border-top: 1px solid var(--border-primary);
|
|
454
|
+
padding-top: 8px;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.offline-sessions-section.collapsed .offline-sessions-list {
|
|
458
|
+
display: none;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.offline-sessions-header {
|
|
462
|
+
display: flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
gap: 8px;
|
|
465
|
+
padding: 4px 12px;
|
|
466
|
+
font-size: 11px;
|
|
467
|
+
font-weight: 600;
|
|
468
|
+
color: var(--text-tertiary);
|
|
469
|
+
text-transform: uppercase;
|
|
470
|
+
letter-spacing: 0.5px;
|
|
471
|
+
cursor: pointer;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.offline-sessions-header:hover {
|
|
475
|
+
color: var(--text-secondary);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.offline-sessions-header .collapse-icon {
|
|
479
|
+
font-size: 8px;
|
|
480
|
+
transition: transform 0.15s;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.offline-sessions-section.collapsed .collapse-icon {
|
|
484
|
+
transform: rotate(-90deg);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.offline-sessions-count {
|
|
488
|
+
background: var(--bg-tertiary);
|
|
489
|
+
color: var(--text-tertiary);
|
|
490
|
+
padding: 1px 6px;
|
|
491
|
+
border-radius: 10px;
|
|
492
|
+
font-size: 10px;
|
|
493
|
+
font-weight: 500;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.offline-sessions-list {
|
|
497
|
+
display: flex;
|
|
498
|
+
flex-direction: column;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* Offline session groups have muted styling */
|
|
502
|
+
.offline-sessions-list .session-group {
|
|
503
|
+
opacity: 0.7;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.offline-sessions-list .session-group:hover {
|
|
507
|
+
opacity: 1;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.offline-sessions-list .session-group-icon {
|
|
511
|
+
filter: grayscale(40%);
|
|
512
|
+
}
|
|
513
|
+
|
|
450
514
|
/* Session Group - Expandable container */
|
|
451
515
|
.session-group {
|
|
452
516
|
margin-bottom: 2px;
|
|
@@ -2552,6 +2616,77 @@
|
|
|
2552
2616
|
min-height: 80px;
|
|
2553
2617
|
}
|
|
2554
2618
|
|
|
2619
|
+
.form-group select {
|
|
2620
|
+
width: 100%;
|
|
2621
|
+
padding: 10px 12px;
|
|
2622
|
+
background: var(--bg-primary);
|
|
2623
|
+
border: 1px solid var(--border);
|
|
2624
|
+
border-radius: 6px;
|
|
2625
|
+
font-size: 14px;
|
|
2626
|
+
color: var(--text-primary);
|
|
2627
|
+
font-family: inherit;
|
|
2628
|
+
cursor: pointer;
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
.form-group select:focus {
|
|
2632
|
+
outline: none;
|
|
2633
|
+
border-color: var(--accent);
|
|
2634
|
+
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
.form-group {
|
|
2638
|
+
position: relative;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
.autocomplete-dropdown {
|
|
2642
|
+
position: absolute;
|
|
2643
|
+
top: 100%;
|
|
2644
|
+
left: 0;
|
|
2645
|
+
right: 0;
|
|
2646
|
+
background: var(--bg-secondary);
|
|
2647
|
+
border: 1px solid var(--border);
|
|
2648
|
+
border-radius: 6px;
|
|
2649
|
+
max-height: 200px;
|
|
2650
|
+
overflow-y: auto;
|
|
2651
|
+
z-index: 1000;
|
|
2652
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
2653
|
+
margin-top: 4px;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
.autocomplete-item {
|
|
2657
|
+
padding: 10px 12px;
|
|
2658
|
+
cursor: pointer;
|
|
2659
|
+
font-size: 13px;
|
|
2660
|
+
border-bottom: 1px solid var(--border);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
.autocomplete-item:last-child {
|
|
2664
|
+
border-bottom: none;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
.autocomplete-item:hover {
|
|
2668
|
+
background: var(--bg-tertiary);
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
.autocomplete-item .path {
|
|
2672
|
+
color: var(--text-primary);
|
|
2673
|
+
font-family: 'SF Mono', monospace;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
.autocomplete-item .name {
|
|
2677
|
+
color: var(--text-tertiary);
|
|
2678
|
+
font-size: 11px;
|
|
2679
|
+
margin-top: 2px;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
.autocomplete-item.type-known {
|
|
2683
|
+
border-left: 3px solid var(--accent);
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
.autocomplete-item.type-filesystem {
|
|
2687
|
+
border-left: 3px solid var(--text-tertiary);
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2555
2690
|
.modal-actions {
|
|
2556
2691
|
display: flex;
|
|
2557
2692
|
justify-content: flex-end;
|
|
@@ -2672,670 +2807,111 @@
|
|
|
2672
2807
|
justify-content: center;
|
|
2673
2808
|
font-size: 24px;
|
|
2674
2809
|
}
|
|
2810
|
+
</style>
|
|
2811
|
+
</head>
|
|
2812
|
+
<body>
|
|
2813
|
+
<aside class="sidebar">
|
|
2814
|
+
<div class="sidebar-header">
|
|
2815
|
+
<div class="logo">
|
|
2816
|
+
<div class="logo-icon">C</div>
|
|
2817
|
+
<span>Claude Code</span>
|
|
2818
|
+
<span class="attention-badge hidden" id="attention-badge">0</span>
|
|
2819
|
+
</div>
|
|
2820
|
+
</div>
|
|
2675
2821
|
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
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
|
-
}
|
|
2822
|
+
<!-- Managed Sessions Panel - Linear-style -->
|
|
2823
|
+
<div class="managed-sessions-panel">
|
|
2824
|
+
<div class="managed-sessions-header">
|
|
2825
|
+
<span class="managed-sessions-title">Sessions</span>
|
|
2826
|
+
<span class="managed-sessions-count" id="managed-sessions-count">0</span>
|
|
2827
|
+
</div>
|
|
2703
2828
|
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
align-items: center;
|
|
2707
|
-
gap: 8px;
|
|
2708
|
-
font-size: 14px;
|
|
2709
|
-
font-weight: 600;
|
|
2710
|
-
}
|
|
2829
|
+
<!-- Managed Sessions List -->
|
|
2830
|
+
<div class="managed-sessions-list" id="managed-sessions-list"></div>
|
|
2711
2831
|
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2832
|
+
<!-- Hidden Sessions Section -->
|
|
2833
|
+
<div class="hidden-sessions-section" id="hidden-sessions-section" style="display: none;">
|
|
2834
|
+
<div class="hidden-sessions-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
2835
|
+
<span>Hidden</span>
|
|
2836
|
+
<span class="collapse-icon">▼</span>
|
|
2837
|
+
</div>
|
|
2838
|
+
<div class="hidden-sessions-list"></div>
|
|
2839
|
+
</div>
|
|
2717
2840
|
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2841
|
+
<!-- Create Session Button -->
|
|
2842
|
+
<div style="padding: 8px;">
|
|
2843
|
+
<button class="create-session-btn" onclick="alert('clicked'); document.getElementById('launch-session-modal').classList.add('open');" title="Launch a new Claude Code session in tmux">
|
|
2844
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2845
|
+
<path d="M12 5v14M5 12h14"/>
|
|
2846
|
+
</svg>
|
|
2847
|
+
Launch Session
|
|
2848
|
+
</button>
|
|
2849
|
+
</div>
|
|
2850
|
+
</div>
|
|
2723
2851
|
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2852
|
+
<!-- Task Sessions (from Claude tasks dir) - hidden by default, sessions auto-discovered from hooks -->
|
|
2853
|
+
<div class="sidebar-section collapsed" id="task-sessions-section" style="display: none;">
|
|
2854
|
+
<div class="sidebar-label" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
2855
|
+
Task Files
|
|
2856
|
+
<span class="collapse-icon">▼</span>
|
|
2857
|
+
</div>
|
|
2858
|
+
<ul class="session-list" id="session-list"></ul>
|
|
2859
|
+
</div>
|
|
2732
2860
|
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2861
|
+
<div class="sidebar-footer">
|
|
2862
|
+
<span class="live-dot" id="live-dot"></span>
|
|
2863
|
+
<span id="live-status">Connected</span>
|
|
2864
|
+
</div>
|
|
2865
|
+
</aside>
|
|
2737
2866
|
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
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
|
-
}
|
|
3227
|
-
</style>
|
|
3228
|
-
</head>
|
|
3229
|
-
<body>
|
|
3230
|
-
<aside class="sidebar">
|
|
3231
|
-
<div class="sidebar-header">
|
|
3232
|
-
<div class="logo">
|
|
3233
|
-
<div class="logo-icon">C</div>
|
|
3234
|
-
<span>Claude Code</span>
|
|
3235
|
-
<span class="attention-badge hidden" id="attention-badge">0</span>
|
|
3236
|
-
</div>
|
|
3237
|
-
</div>
|
|
3238
|
-
|
|
3239
|
-
<!-- Managed Sessions Panel - Linear-style -->
|
|
3240
|
-
<div class="managed-sessions-panel">
|
|
3241
|
-
<div class="managed-sessions-header">
|
|
3242
|
-
<span class="managed-sessions-title">Sessions</span>
|
|
3243
|
-
<span class="managed-sessions-count" id="managed-sessions-count">0</span>
|
|
3244
|
-
</div>
|
|
3245
|
-
|
|
3246
|
-
<!-- Managed Sessions List -->
|
|
3247
|
-
<div class="managed-sessions-list" id="managed-sessions-list"></div>
|
|
3248
|
-
|
|
3249
|
-
<!-- Hidden Sessions Section -->
|
|
3250
|
-
<div class="hidden-sessions-section" id="hidden-sessions-section" style="display: none;">
|
|
3251
|
-
<div class="hidden-sessions-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
3252
|
-
<span>Hidden</span>
|
|
3253
|
-
<span class="collapse-icon">▼</span>
|
|
3254
|
-
</div>
|
|
3255
|
-
<div class="hidden-sessions-list"></div>
|
|
3256
|
-
</div>
|
|
3257
|
-
|
|
3258
|
-
<!-- Create Session Button -->
|
|
3259
|
-
<div style="padding: 8px;">
|
|
3260
|
-
<button class="create-session-btn" onclick="openCreateSessionModal()" title="Launch a new Claude Code session in tmux">
|
|
3261
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3262
|
-
<path d="M12 5v14M5 12h14"/>
|
|
3263
|
-
</svg>
|
|
3264
|
-
Launch Session
|
|
3265
|
-
</button>
|
|
3266
|
-
</div>
|
|
3267
|
-
</div>
|
|
3268
|
-
|
|
3269
|
-
<!-- Task Sessions (from Claude tasks dir) - hidden by default, sessions auto-discovered from hooks -->
|
|
3270
|
-
<div class="sidebar-section collapsed" id="task-sessions-section" style="display: none;">
|
|
3271
|
-
<div class="sidebar-label" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
3272
|
-
Task Files
|
|
3273
|
-
<span class="collapse-icon">▼</span>
|
|
3274
|
-
</div>
|
|
3275
|
-
<ul class="session-list" id="session-list"></ul>
|
|
3276
|
-
</div>
|
|
3277
|
-
|
|
3278
|
-
<div class="sidebar-footer">
|
|
3279
|
-
<span class="live-dot" id="live-dot"></span>
|
|
3280
|
-
<span id="live-status">Connected</span>
|
|
3281
|
-
</div>
|
|
3282
|
-
</aside>
|
|
3283
|
-
|
|
3284
|
-
<main class="main">
|
|
3285
|
-
<header class="header">
|
|
3286
|
-
<div class="header-left">
|
|
3287
|
-
<h1 class="header-title" id="header-title">All Tasks</h1>
|
|
3288
|
-
<div class="view-tabs">
|
|
3289
|
-
<button class="view-tab active" data-view="list">List</button>
|
|
3290
|
-
<button class="view-tab" data-view="kanban">Board</button>
|
|
3291
|
-
</div>
|
|
3292
|
-
</div>
|
|
3293
|
-
<div class="header-right">
|
|
3294
|
-
<button class="next-attention-btn hidden" id="next-attention-btn" onclick="goToNextAttention()">
|
|
3295
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3296
|
-
<circle cx="12" cy="12" r="10"/>
|
|
3297
|
-
<path d="M12 16v-4M12 8h.01"/>
|
|
3298
|
-
</svg>
|
|
3299
|
-
Next (<span id="attention-count">0</span>)
|
|
3300
|
-
</button>
|
|
3301
|
-
<button class="create-task-btn" onclick="openCreateTaskModal()">
|
|
3302
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3303
|
-
<path d="M12 5v14M5 12h14"/>
|
|
3304
|
-
</svg>
|
|
3305
|
-
New Task
|
|
3306
|
-
</button>
|
|
3307
|
-
<button class="conv-toggle-btn" onclick="toggleConversation()" id="conv-toggle">
|
|
3308
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3309
|
-
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
|
|
3310
|
-
</svg>
|
|
3311
|
-
Conversation
|
|
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>
|
|
3320
|
-
<button class="filter-btn" onclick="toggleTheme()" id="theme-toggle">
|
|
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">
|
|
3322
|
-
<circle cx="12" cy="12" r="5"/>
|
|
3323
|
-
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
3324
|
-
</svg>
|
|
3325
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="theme-icon-light">
|
|
3326
|
-
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
|
3327
|
-
</svg>
|
|
3328
|
-
<span id="theme-label">Light</span>
|
|
3329
|
-
</button>
|
|
3330
|
-
<button class="filter-btn" onclick="refresh()">
|
|
3331
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3332
|
-
<path d="M23 4v6h-6M1 20v-6h6"/>
|
|
3333
|
-
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/>
|
|
3334
|
-
</svg>
|
|
3335
|
-
Refresh
|
|
3336
|
-
</button>
|
|
3337
|
-
</div>
|
|
3338
|
-
</header>
|
|
2867
|
+
<main class="main">
|
|
2868
|
+
<header class="header">
|
|
2869
|
+
<div class="header-left">
|
|
2870
|
+
<h1 class="header-title" id="header-title">All Tasks</h1>
|
|
2871
|
+
<div class="view-tabs">
|
|
2872
|
+
<button class="view-tab active" data-view="list">List</button>
|
|
2873
|
+
<button class="view-tab" data-view="kanban">Board</button>
|
|
2874
|
+
</div>
|
|
2875
|
+
</div>
|
|
2876
|
+
<div class="header-right">
|
|
2877
|
+
<button class="next-attention-btn hidden" id="next-attention-btn" onclick="goToNextAttention()">
|
|
2878
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2879
|
+
<circle cx="12" cy="12" r="10"/>
|
|
2880
|
+
<path d="M12 16v-4M12 8h.01"/>
|
|
2881
|
+
</svg>
|
|
2882
|
+
Next (<span id="attention-count">0</span>)
|
|
2883
|
+
</button>
|
|
2884
|
+
<button class="create-task-btn" onclick="openCreateTaskModal()">
|
|
2885
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2886
|
+
<path d="M12 5v14M5 12h14"/>
|
|
2887
|
+
</svg>
|
|
2888
|
+
New Task
|
|
2889
|
+
</button>
|
|
2890
|
+
<button class="conv-toggle-btn" onclick="toggleConversation()" id="conv-toggle">
|
|
2891
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2892
|
+
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
|
|
2893
|
+
</svg>
|
|
2894
|
+
Conversation
|
|
2895
|
+
</button>
|
|
2896
|
+
<button class="filter-btn" onclick="toggleTheme()" id="theme-toggle">
|
|
2897
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="theme-icon-dark" style="display:none">
|
|
2898
|
+
<circle cx="12" cy="12" r="5"/>
|
|
2899
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
2900
|
+
</svg>
|
|
2901
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="theme-icon-light">
|
|
2902
|
+
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
|
2903
|
+
</svg>
|
|
2904
|
+
<span id="theme-label">Light</span>
|
|
2905
|
+
</button>
|
|
2906
|
+
<button class="filter-btn" onclick="refresh()">
|
|
2907
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2908
|
+
<path d="M23 4v6h-6M1 20v-6h6"/>
|
|
2909
|
+
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/>
|
|
2910
|
+
</svg>
|
|
2911
|
+
Refresh
|
|
2912
|
+
</button>
|
|
2913
|
+
</div>
|
|
2914
|
+
</header>
|
|
3339
2915
|
|
|
3340
2916
|
<div class="stats-bar" id="stats-bar">
|
|
3341
2917
|
<div class="stat" id="filter-pending" onclick="toggleFilter('pending')">
|
|
@@ -3439,93 +3015,6 @@
|
|
|
3439
3015
|
</div>
|
|
3440
3016
|
</div>
|
|
3441
3017
|
|
|
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
|
-
|
|
3529
3018
|
<div class="toast" id="toast">
|
|
3530
3019
|
<svg class="toast-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3531
3020
|
<path d="M20 6L9 17l-5-5"/>
|
|
@@ -3598,6 +3087,41 @@
|
|
|
3598
3087
|
</div>
|
|
3599
3088
|
</div>
|
|
3600
3089
|
|
|
3090
|
+
<!-- Launch Session Modal -->
|
|
3091
|
+
<div class="modal-overlay" id="launch-session-modal" onclick="closeLaunchSessionModal(event)">
|
|
3092
|
+
<div class="modal" onclick="event.stopPropagation()" style="max-width: 500px;">
|
|
3093
|
+
<div class="modal-header">
|
|
3094
|
+
<h3>Launch New Session</h3>
|
|
3095
|
+
<button class="close-btn" onclick="closeLaunchSessionModal()">×</button>
|
|
3096
|
+
</div>
|
|
3097
|
+
<form class="modal-form" id="launch-session-form" onsubmit="submitLaunchSession(event)">
|
|
3098
|
+
<div class="form-group">
|
|
3099
|
+
<label for="launch-session-name">Session Name (optional)</label>
|
|
3100
|
+
<input type="text" id="launch-session-name" placeholder="My Project">
|
|
3101
|
+
</div>
|
|
3102
|
+
<div class="form-group">
|
|
3103
|
+
<label for="launch-session-cwd">Working Directory</label>
|
|
3104
|
+
<input type="text" id="launch-session-cwd" placeholder="/path/to/project" required autocomplete="off">
|
|
3105
|
+
<div id="cwd-autocomplete" class="autocomplete-dropdown" style="display: none;"></div>
|
|
3106
|
+
<div class="form-hint">The directory where Claude Code will run</div>
|
|
3107
|
+
</div>
|
|
3108
|
+
<div class="form-group">
|
|
3109
|
+
<label for="launch-session-model">Model (optional)</label>
|
|
3110
|
+
<select id="launch-session-model">
|
|
3111
|
+
<option value="">Default (Sonnet)</option>
|
|
3112
|
+
<option value="sonnet">Sonnet</option>
|
|
3113
|
+
<option value="opus">Opus</option>
|
|
3114
|
+
<option value="haiku">Haiku</option>
|
|
3115
|
+
</select>
|
|
3116
|
+
</div>
|
|
3117
|
+
<div class="modal-actions">
|
|
3118
|
+
<button type="button" class="btn-secondary" onclick="closeLaunchSessionModal()">Cancel</button>
|
|
3119
|
+
<button type="submit" class="btn-primary">Launch Session</button>
|
|
3120
|
+
</div>
|
|
3121
|
+
</form>
|
|
3122
|
+
</div>
|
|
3123
|
+
</div>
|
|
3124
|
+
|
|
3601
3125
|
<!-- Bulk Action Bar -->
|
|
3602
3126
|
<div id="bulk-action-bar" class="bulk-action-bar" style="display: none;">
|
|
3603
3127
|
<span class="bulk-count">0 selected</span>
|
|
@@ -3675,6 +3199,15 @@
|
|
|
3675
3199
|
// Track expanded state for each session
|
|
3676
3200
|
const expandedSessions = new Set();
|
|
3677
3201
|
|
|
3202
|
+
// Track collapsed state for offline sessions section (collapsed by default)
|
|
3203
|
+
let offlineSectionCollapsed = true;
|
|
3204
|
+
|
|
3205
|
+
// Toggle offline sessions section
|
|
3206
|
+
function toggleOfflineSessionsSection() {
|
|
3207
|
+
offlineSectionCollapsed = !offlineSectionCollapsed;
|
|
3208
|
+
renderManagedSessions();
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3678
3211
|
// Render managed sessions list - Linear-style with expandable groups
|
|
3679
3212
|
function renderManagedSessions() {
|
|
3680
3213
|
const list = document.getElementById('managed-sessions-list');
|
|
@@ -3682,9 +3215,13 @@
|
|
|
3682
3215
|
|
|
3683
3216
|
if (!list) return;
|
|
3684
3217
|
|
|
3685
|
-
//
|
|
3218
|
+
// Separate active and offline sessions
|
|
3219
|
+
const activeSessions = managedSessions.filter(s => s.status !== 'offline');
|
|
3220
|
+
const offlineSessions = managedSessions.filter(s => s.status === 'offline');
|
|
3221
|
+
|
|
3222
|
+
// Update count (show only active sessions count)
|
|
3686
3223
|
if (countEl) {
|
|
3687
|
-
countEl.textContent =
|
|
3224
|
+
countEl.textContent = activeSessions.length;
|
|
3688
3225
|
}
|
|
3689
3226
|
|
|
3690
3227
|
// SVG icons
|
|
@@ -3694,14 +3231,8 @@
|
|
|
3694
3231
|
const plansIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>';
|
|
3695
3232
|
const inboxIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>';
|
|
3696
3233
|
|
|
3697
|
-
//
|
|
3698
|
-
|
|
3699
|
-
'<span class="view-icon">' + inboxIcon + '</span>' +
|
|
3700
|
-
'<span class="view-name">All Sessions</span>' +
|
|
3701
|
-
'</div>';
|
|
3702
|
-
|
|
3703
|
-
// Render session groups
|
|
3704
|
-
list.innerHTML = allSessionsHtml + managedSessions.map(session => {
|
|
3234
|
+
// Helper function to render a session group
|
|
3235
|
+
function renderSessionGroup(session) {
|
|
3705
3236
|
const isExpanded = expandedSessions.has(session.id);
|
|
3706
3237
|
const isActiveSession = selectedManagedSession === session.id;
|
|
3707
3238
|
const statusClass = session.status || 'offline';
|
|
@@ -3757,7 +3288,34 @@
|
|
|
3757
3288
|
'</div>' +
|
|
3758
3289
|
'</div>' +
|
|
3759
3290
|
'</div>';
|
|
3760
|
-
}
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
// "All Sessions" item at top
|
|
3294
|
+
const allSessionsHtml = '<div class="session-view-item ' + (selectedManagedSession === null ? 'active' : '') + '" onclick="selectManagedSession(null)" style="margin: 0 4px 8px 4px;">' +
|
|
3295
|
+
'<span class="view-icon">' + inboxIcon + '</span>' +
|
|
3296
|
+
'<span class="view-name">All Sessions</span>' +
|
|
3297
|
+
'</div>';
|
|
3298
|
+
|
|
3299
|
+
// Render active sessions
|
|
3300
|
+
const activeSessionsHtml = activeSessions.map(renderSessionGroup).join('');
|
|
3301
|
+
|
|
3302
|
+
// Render offline sessions section (only if there are offline sessions)
|
|
3303
|
+
let offlineSessionsHtml = '';
|
|
3304
|
+
if (offlineSessions.length > 0) {
|
|
3305
|
+
offlineSessionsHtml = '<div class="offline-sessions-section ' + (offlineSectionCollapsed ? 'collapsed' : '') + '">' +
|
|
3306
|
+
'<div class="offline-sessions-header" onclick="toggleOfflineSessionsSection()">' +
|
|
3307
|
+
'<span class="collapse-icon">▼</span>' +
|
|
3308
|
+
'<span>Offline</span>' +
|
|
3309
|
+
'<span class="offline-sessions-count">' + offlineSessions.length + '</span>' +
|
|
3310
|
+
'</div>' +
|
|
3311
|
+
'<div class="offline-sessions-list">' +
|
|
3312
|
+
offlineSessions.map(renderSessionGroup).join('') +
|
|
3313
|
+
'</div>' +
|
|
3314
|
+
'</div>';
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
// Combine all HTML
|
|
3318
|
+
list.innerHTML = allSessionsHtml + activeSessionsHtml + offlineSessionsHtml;
|
|
3761
3319
|
}
|
|
3762
3320
|
|
|
3763
3321
|
// Track current view within a session
|
|
@@ -4064,43 +3622,220 @@
|
|
|
4064
3622
|
const initials = session.name.substring(0, 2).toUpperCase();
|
|
4065
3623
|
const escapedName = escapeHtml(session.name).replace(/'/g, "\\'");
|
|
4066
3624
|
|
|
4067
|
-
return '<div class="hidden-session-item">' +
|
|
4068
|
-
'<div class="session-group-icon" style="background: ' + bgColor + '; color: white; width: 20px; height: 20px; font-size: 9px;">' + initials + '</div>' +
|
|
4069
|
-
'<span class="hidden-session-name">' + escapeHtml(session.name) + '</span>' +
|
|
4070
|
-
'<button class="unhide-btn" onclick="unhideSession(\'' + session.id + '\')" title="Unhide session">' + eyeIcon + '</button>' +
|
|
4071
|
-
'<button class="delete-btn" onclick="permanentDeleteSession(\'' + session.id + '\', \'' + escapedName + '\')" title="Permanently delete">' + trashIcon + '</button>' +
|
|
4072
|
-
'</div>';
|
|
4073
|
-
}).join('');
|
|
3625
|
+
return '<div class="hidden-session-item">' +
|
|
3626
|
+
'<div class="session-group-icon" style="background: ' + bgColor + '; color: white; width: 20px; height: 20px; font-size: 9px;">' + initials + '</div>' +
|
|
3627
|
+
'<span class="hidden-session-name">' + escapeHtml(session.name) + '</span>' +
|
|
3628
|
+
'<button class="unhide-btn" onclick="unhideSession(\'' + session.id + '\')" title="Unhide session">' + eyeIcon + '</button>' +
|
|
3629
|
+
'<button class="delete-btn" onclick="permanentDeleteSession(\'' + session.id + '\', \'' + escapedName + '\')" title="Permanently delete">' + trashIcon + '</button>' +
|
|
3630
|
+
'</div>';
|
|
3631
|
+
}).join('');
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
// Restart managed session
|
|
3635
|
+
async function restartManagedSession(sessionId) {
|
|
3636
|
+
try {
|
|
3637
|
+
const res = await fetch('/api/managed-sessions/' + sessionId + '/restart', {
|
|
3638
|
+
method: 'POST'
|
|
3639
|
+
});
|
|
3640
|
+
const data = await res.json();
|
|
3641
|
+
if (data.ok) {
|
|
3642
|
+
loadManagedSessions();
|
|
3643
|
+
} else {
|
|
3644
|
+
alert('Failed to restart: ' + (data.error || 'Unknown error'));
|
|
3645
|
+
}
|
|
3646
|
+
} catch (e) {
|
|
3647
|
+
console.error('Failed to restart session:', e);
|
|
3648
|
+
alert('Failed to restart session');
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
// Open launch session modal
|
|
3653
|
+
function openCreateSessionModal() {
|
|
3654
|
+
alert('Function called!'); // Debug - remove after testing
|
|
3655
|
+
|
|
3656
|
+
const modal = document.getElementById('launch-session-modal');
|
|
3657
|
+
if (!modal) {
|
|
3658
|
+
alert('Modal not found!');
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
alert('Modal found, adding open class'); // Debug - remove after testing
|
|
3663
|
+
|
|
3664
|
+
const form = document.getElementById('launch-session-form');
|
|
3665
|
+
const cwdInput = document.getElementById('launch-session-cwd');
|
|
3666
|
+
const nameInput = document.getElementById('launch-session-name');
|
|
3667
|
+
|
|
3668
|
+
if (form) form.reset();
|
|
3669
|
+
|
|
3670
|
+
// Load recent projects for autocomplete
|
|
3671
|
+
loadRecentProjects();
|
|
3672
|
+
|
|
3673
|
+
// Set up autocomplete
|
|
3674
|
+
if (cwdInput) setupCwdAutocomplete(cwdInput);
|
|
3675
|
+
|
|
3676
|
+
modal.classList.add('open');
|
|
3677
|
+
|
|
3678
|
+
if (nameInput) nameInput.focus();
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
function closeLaunchSessionModal(event) {
|
|
3682
|
+
if (event && event.target !== event.currentTarget) return;
|
|
3683
|
+
const modal = document.getElementById('launch-session-modal');
|
|
3684
|
+
modal.classList.remove('open');
|
|
3685
|
+
hideAutocomplete();
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// Set up working directory autocomplete
|
|
3689
|
+
let autocompleteTimeout = null;
|
|
3690
|
+
function setupCwdAutocomplete(input) {
|
|
3691
|
+
input.addEventListener('input', (e) => {
|
|
3692
|
+
clearTimeout(autocompleteTimeout);
|
|
3693
|
+
autocompleteTimeout = setTimeout(() => {
|
|
3694
|
+
fetchAutocomplete(e.target.value);
|
|
3695
|
+
}, 150);
|
|
3696
|
+
});
|
|
3697
|
+
|
|
3698
|
+
input.addEventListener('keydown', (e) => {
|
|
3699
|
+
const dropdown = document.getElementById('cwd-autocomplete');
|
|
3700
|
+
const items = dropdown.querySelectorAll('.autocomplete-item');
|
|
3701
|
+
const activeItem = dropdown.querySelector('.autocomplete-item.active');
|
|
3702
|
+
|
|
3703
|
+
if (e.key === 'ArrowDown') {
|
|
3704
|
+
e.preventDefault();
|
|
3705
|
+
if (!activeItem && items.length > 0) {
|
|
3706
|
+
items[0].classList.add('active');
|
|
3707
|
+
} else if (activeItem && activeItem.nextElementSibling) {
|
|
3708
|
+
activeItem.classList.remove('active');
|
|
3709
|
+
activeItem.nextElementSibling.classList.add('active');
|
|
3710
|
+
}
|
|
3711
|
+
} else if (e.key === 'ArrowUp') {
|
|
3712
|
+
e.preventDefault();
|
|
3713
|
+
if (activeItem && activeItem.previousElementSibling) {
|
|
3714
|
+
activeItem.classList.remove('active');
|
|
3715
|
+
activeItem.previousElementSibling.classList.add('active');
|
|
3716
|
+
}
|
|
3717
|
+
} else if (e.key === 'Enter' && activeItem) {
|
|
3718
|
+
e.preventDefault();
|
|
3719
|
+
selectAutocompleteItem(activeItem.dataset.path);
|
|
3720
|
+
} else if (e.key === 'Escape') {
|
|
3721
|
+
hideAutocomplete();
|
|
3722
|
+
}
|
|
3723
|
+
});
|
|
3724
|
+
|
|
3725
|
+
input.addEventListener('blur', () => {
|
|
3726
|
+
// Delay hiding to allow click on item
|
|
3727
|
+
setTimeout(hideAutocomplete, 200);
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
async function fetchAutocomplete(query) {
|
|
3732
|
+
try {
|
|
3733
|
+
const res = await fetch(`/api/spawner/projects/autocomplete?q=${encodeURIComponent(query)}`);
|
|
3734
|
+
const data = await res.json();
|
|
3735
|
+
if (data.ok && data.suggestions) {
|
|
3736
|
+
showAutocomplete(data.suggestions);
|
|
3737
|
+
}
|
|
3738
|
+
} catch (e) {
|
|
3739
|
+
console.error('Autocomplete error:', e);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
function showAutocomplete(suggestions) {
|
|
3744
|
+
const dropdown = document.getElementById('cwd-autocomplete');
|
|
3745
|
+
|
|
3746
|
+
if (suggestions.length === 0) {
|
|
3747
|
+
hideAutocomplete();
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
dropdown.innerHTML = suggestions.map(s => `
|
|
3752
|
+
<div class="autocomplete-item type-${s.type || 'filesystem'}"
|
|
3753
|
+
data-path="${escapeHtml(s.path)}"
|
|
3754
|
+
onclick="selectAutocompleteItem(this.dataset.path)">
|
|
3755
|
+
<div class="path">${escapeHtml(s.path)}</div>
|
|
3756
|
+
${s.name && s.name !== s.path ? `<div class="name">${escapeHtml(s.name)}</div>` : ''}
|
|
3757
|
+
</div>
|
|
3758
|
+
`).join('');
|
|
3759
|
+
|
|
3760
|
+
dropdown.style.display = 'block';
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
function hideAutocomplete() {
|
|
3764
|
+
const dropdown = document.getElementById('cwd-autocomplete');
|
|
3765
|
+
dropdown.style.display = 'none';
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
function selectAutocompleteItem(path) {
|
|
3769
|
+
const input = document.getElementById('launch-session-cwd');
|
|
3770
|
+
input.value = path;
|
|
3771
|
+
hideAutocomplete();
|
|
3772
|
+
input.focus();
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
async function loadRecentProjects() {
|
|
3776
|
+
try {
|
|
3777
|
+
const res = await fetch('/api/spawner/projects?limit=5');
|
|
3778
|
+
const data = await res.json();
|
|
3779
|
+
// Could show recent projects as suggestions, but for now just preload
|
|
3780
|
+
} catch (e) {
|
|
3781
|
+
// Ignore
|
|
3782
|
+
}
|
|
4074
3783
|
}
|
|
4075
3784
|
|
|
4076
|
-
//
|
|
4077
|
-
async function
|
|
3785
|
+
// Submit launch session form
|
|
3786
|
+
async function submitLaunchSession(event) {
|
|
3787
|
+
event.preventDefault();
|
|
3788
|
+
|
|
3789
|
+
const name = document.getElementById('launch-session-name').value.trim();
|
|
3790
|
+
const cwd = document.getElementById('launch-session-cwd').value.trim();
|
|
3791
|
+
const model = document.getElementById('launch-session-model').value;
|
|
3792
|
+
|
|
3793
|
+
if (!cwd) {
|
|
3794
|
+
alert('Working directory is required');
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
|
|
4078
3798
|
try {
|
|
4079
|
-
|
|
4080
|
-
|
|
3799
|
+
// Try the new spawner API first
|
|
3800
|
+
const res = await fetch('/api/spawner/sessions', {
|
|
3801
|
+
method: 'POST',
|
|
3802
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3803
|
+
body: JSON.stringify({
|
|
3804
|
+
name: name || undefined,
|
|
3805
|
+
cwd,
|
|
3806
|
+
model: model || undefined
|
|
3807
|
+
})
|
|
4081
3808
|
});
|
|
4082
3809
|
const data = await res.json();
|
|
3810
|
+
|
|
4083
3811
|
if (data.ok) {
|
|
3812
|
+
closeLaunchSessionModal();
|
|
4084
3813
|
loadManagedSessions();
|
|
3814
|
+
// Also refresh spawned sessions
|
|
3815
|
+
loadSpawnedSessions();
|
|
3816
|
+
showToast('Session launched successfully');
|
|
4085
3817
|
} else {
|
|
4086
|
-
alert('Failed to
|
|
3818
|
+
alert('Failed to launch session: ' + (data.error || 'Unknown error'));
|
|
4087
3819
|
}
|
|
4088
3820
|
} catch (e) {
|
|
4089
|
-
console.error('Failed to
|
|
4090
|
-
alert('Failed to
|
|
3821
|
+
console.error('Failed to launch session:', e);
|
|
3822
|
+
alert('Failed to launch session: ' + e.message);
|
|
4091
3823
|
}
|
|
4092
3824
|
}
|
|
4093
3825
|
|
|
4094
|
-
//
|
|
4095
|
-
function
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
3826
|
+
// Load spawned sessions (from new spawner API)
|
|
3827
|
+
async function loadSpawnedSessions() {
|
|
3828
|
+
try {
|
|
3829
|
+
const res = await fetch('/api/spawner/sessions');
|
|
3830
|
+
const data = await res.json();
|
|
3831
|
+
// For now, just log - could merge with managed sessions display
|
|
3832
|
+
console.log('Spawned sessions:', data.sessions);
|
|
3833
|
+
} catch (e) {
|
|
3834
|
+
// Ignore errors
|
|
3835
|
+
}
|
|
4101
3836
|
}
|
|
4102
3837
|
|
|
4103
|
-
//
|
|
3838
|
+
// Legacy function for backwards compatibility
|
|
4104
3839
|
async function createManagedSession(name, cwd) {
|
|
4105
3840
|
try {
|
|
4106
3841
|
const res = await fetch('/api/managed-sessions', {
|
|
@@ -5100,22 +4835,6 @@
|
|
|
5100
4835
|
renderManagedSessions();
|
|
5101
4836
|
}
|
|
5102
4837
|
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;
|
|
5119
4838
|
case 'update':
|
|
5120
4839
|
default:
|
|
5121
4840
|
// Fallback to full reload for unknown event types
|
|
@@ -7422,584 +7141,6 @@
|
|
|
7422
7141
|
console.error('Failed to load Claude events:', e);
|
|
7423
7142
|
}
|
|
7424
7143
|
}
|
|
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
|
-
}
|
|
8003
7144
|
</script>
|
|
8004
7145
|
</body>
|
|
8005
7146
|
</html>
|