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/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
- /* 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
- }
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
- .orchestration-header h3 {
2705
- display: flex;
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
- .orchestration-content {
2713
- flex: 1;
2714
- overflow-y: auto;
2715
- padding: 16px;
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
- .orchestration-list {
2719
- display: flex;
2720
- flex-direction: column;
2721
- gap: 12px;
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
- .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
- }
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
- .orchestration-card:hover {
2734
- border-color: var(--accent);
2735
- background: var(--bg-hover);
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
- .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
- }
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()">&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
-
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()">&times;</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
- // Update count
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 = managedSessions.length;
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
- // "All Sessions" item at top
3698
- const allSessionsHtml = '<div class="session-view-item ' + (selectedManagedSession === null ? 'active' : '') + '" onclick="selectManagedSession(null)" style="margin: 0 4px 8px 4px;">' +
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
- }).join('');
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
- // Restart managed session
4077
- async function restartManagedSession(sessionId) {
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
- const res = await fetch('/api/managed-sessions/' + sessionId + '/restart', {
4080
- method: 'POST'
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 restart: ' + (data.error || 'Unknown error'));
3818
+ alert('Failed to launch session: ' + (data.error || 'Unknown error'));
4087
3819
  }
4088
3820
  } catch (e) {
4089
- console.error('Failed to restart session:', e);
4090
- alert('Failed to restart session');
3821
+ console.error('Failed to launch session:', e);
3822
+ alert('Failed to launch session: ' + e.message);
4091
3823
  }
4092
3824
  }
4093
3825
 
4094
- // Open create session modal
4095
- function openCreateSessionModal() {
4096
- const name = prompt('Session name (optional):');
4097
- const cwd = prompt('Working directory:', process?.cwd?.() || '');
4098
- if (cwd === null) return; // Cancelled
4099
-
4100
- createManagedSession(name || undefined, cwd || undefined);
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
- // Create managed session
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>