claudehq 1.0.2 → 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 +39 -1
- package/lib/data/orchestration.js +941 -0
- package/lib/index.js +211 -23
- package/lib/orchestration/executor.js +635 -0
- 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 +399 -18
- package/lib/server.js +0 -9364
package/package.json
CHANGED
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;
|
|
@@ -2705,7 +2840,7 @@
|
|
|
2705
2840
|
|
|
2706
2841
|
<!-- Create Session Button -->
|
|
2707
2842
|
<div style="padding: 8px;">
|
|
2708
|
-
<button class="create-session-btn" onclick="
|
|
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">
|
|
2709
2844
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2710
2845
|
<path d="M12 5v14M5 12h14"/>
|
|
2711
2846
|
</svg>
|
|
@@ -2952,6 +3087,41 @@
|
|
|
2952
3087
|
</div>
|
|
2953
3088
|
</div>
|
|
2954
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
|
+
|
|
2955
3125
|
<!-- Bulk Action Bar -->
|
|
2956
3126
|
<div id="bulk-action-bar" class="bulk-action-bar" style="display: none;">
|
|
2957
3127
|
<span class="bulk-count">0 selected</span>
|
|
@@ -3029,6 +3199,15 @@
|
|
|
3029
3199
|
// Track expanded state for each session
|
|
3030
3200
|
const expandedSessions = new Set();
|
|
3031
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
|
+
|
|
3032
3211
|
// Render managed sessions list - Linear-style with expandable groups
|
|
3033
3212
|
function renderManagedSessions() {
|
|
3034
3213
|
const list = document.getElementById('managed-sessions-list');
|
|
@@ -3036,9 +3215,13 @@
|
|
|
3036
3215
|
|
|
3037
3216
|
if (!list) return;
|
|
3038
3217
|
|
|
3039
|
-
//
|
|
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)
|
|
3040
3223
|
if (countEl) {
|
|
3041
|
-
countEl.textContent =
|
|
3224
|
+
countEl.textContent = activeSessions.length;
|
|
3042
3225
|
}
|
|
3043
3226
|
|
|
3044
3227
|
// SVG icons
|
|
@@ -3048,14 +3231,8 @@
|
|
|
3048
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>';
|
|
3049
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>';
|
|
3050
3233
|
|
|
3051
|
-
//
|
|
3052
|
-
|
|
3053
|
-
'<span class="view-icon">' + inboxIcon + '</span>' +
|
|
3054
|
-
'<span class="view-name">All Sessions</span>' +
|
|
3055
|
-
'</div>';
|
|
3056
|
-
|
|
3057
|
-
// Render session groups
|
|
3058
|
-
list.innerHTML = allSessionsHtml + managedSessions.map(session => {
|
|
3234
|
+
// Helper function to render a session group
|
|
3235
|
+
function renderSessionGroup(session) {
|
|
3059
3236
|
const isExpanded = expandedSessions.has(session.id);
|
|
3060
3237
|
const isActiveSession = selectedManagedSession === session.id;
|
|
3061
3238
|
const statusClass = session.status || 'offline';
|
|
@@ -3111,7 +3288,34 @@
|
|
|
3111
3288
|
'</div>' +
|
|
3112
3289
|
'</div>' +
|
|
3113
3290
|
'</div>';
|
|
3114
|
-
}
|
|
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;
|
|
3115
3319
|
}
|
|
3116
3320
|
|
|
3117
3321
|
// Track current view within a session
|
|
@@ -3445,16 +3649,193 @@
|
|
|
3445
3649
|
}
|
|
3446
3650
|
}
|
|
3447
3651
|
|
|
3448
|
-
// Open
|
|
3652
|
+
// Open launch session modal
|
|
3449
3653
|
function openCreateSessionModal() {
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
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
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
|
|
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
|
+
|
|
3798
|
+
try {
|
|
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
|
+
})
|
|
3808
|
+
});
|
|
3809
|
+
const data = await res.json();
|
|
3810
|
+
|
|
3811
|
+
if (data.ok) {
|
|
3812
|
+
closeLaunchSessionModal();
|
|
3813
|
+
loadManagedSessions();
|
|
3814
|
+
// Also refresh spawned sessions
|
|
3815
|
+
loadSpawnedSessions();
|
|
3816
|
+
showToast('Session launched successfully');
|
|
3817
|
+
} else {
|
|
3818
|
+
alert('Failed to launch session: ' + (data.error || 'Unknown error'));
|
|
3819
|
+
}
|
|
3820
|
+
} catch (e) {
|
|
3821
|
+
console.error('Failed to launch session:', e);
|
|
3822
|
+
alert('Failed to launch session: ' + e.message);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3453
3825
|
|
|
3454
|
-
|
|
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
|
+
}
|
|
3455
3836
|
}
|
|
3456
3837
|
|
|
3457
|
-
//
|
|
3838
|
+
// Legacy function for backwards compatibility
|
|
3458
3839
|
async function createManagedSession(name, cwd) {
|
|
3459
3840
|
try {
|
|
3460
3841
|
const res = await fetch('/api/managed-sessions', {
|