@sergeychuvayev/claude-fleet 0.9.0 → 0.10.0
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/package.json +1 -1
- package/public/app.js +100 -12
- package/public/control.js +1 -0
- package/public/styles.css +51 -0
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -622,9 +622,9 @@ setInterval(() => { if (!document.hidden) tick() },2000)
|
|
|
622
622
|
document.addEventListener('visibilitychange', () => { if (!document.hidden) tick() })
|
|
623
623
|
|
|
624
624
|
// Layout the operator controls: a draggable split between the session list and the
|
|
625
|
-
// inspector, and
|
|
625
|
+
// inspector, and resizable session sections. Sizes are remembered per browser; a storage
|
|
626
626
|
// failure (private window, blocked site data) only costs the remembered size.
|
|
627
|
-
const LAYOUT = { split: 'fleet:split'
|
|
627
|
+
const LAYOUT = { split: 'fleet:split' }
|
|
628
628
|
const SPLIT_DEFAULT = 58, LIST_MIN = 300, DETAIL_MIN = 380
|
|
629
629
|
|
|
630
630
|
function applySplit(percent, { save = true } = {}) {
|
|
@@ -690,18 +690,106 @@ function initSplitter() {
|
|
|
690
690
|
})
|
|
691
691
|
}
|
|
692
692
|
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
let
|
|
693
|
+
// Vertical sections share the same interaction as the session-list divider.
|
|
694
|
+
// Observe the container so saved sizes yield when the viewport or controls change.
|
|
695
|
+
let panelLayoutCleanup = () => {}
|
|
696
696
|
function watchConversation(element) {
|
|
697
|
+
panelLayoutCleanup()
|
|
697
698
|
if (!element) return
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
699
|
+
const container = element.parentElement
|
|
700
|
+
const disposers = []
|
|
701
|
+
const addPanel = (panel, { key, label, min, initial, before = false }) => {
|
|
702
|
+
const divider = document.createElement('div')
|
|
703
|
+
divider.className = 'panel-splitter'
|
|
704
|
+
divider.tabIndex = 0
|
|
705
|
+
divider.setAttribute('role', 'separator')
|
|
706
|
+
divider.setAttribute('aria-orientation', 'horizontal')
|
|
707
|
+
divider.setAttribute('aria-label', label)
|
|
708
|
+
divider.setAttribute('aria-controls', panel.id)
|
|
709
|
+
divider.title = 'Drag to resize · arrow keys to adjust · double-click to reset'
|
|
710
|
+
before ? panel.before(divider) : panel.after(divider)
|
|
711
|
+
const saved = Number(store.get(key))
|
|
712
|
+
let preferred = Number.isFinite(saved) && saved >= min ? saved : initial
|
|
713
|
+
let drag = null
|
|
714
|
+
const height = () => panel.getBoundingClientRect().height
|
|
715
|
+
const maximum = () => Math.max(min, Math.min(container.clientHeight * .45,
|
|
716
|
+
height() + element.clientHeight - 120))
|
|
717
|
+
const apply = () => {
|
|
718
|
+
const mobile = matchMedia('(max-width:720px)').matches
|
|
719
|
+
const collapsed = panel.tagName === 'DETAILS' && !panel.open
|
|
720
|
+
divider.hidden = mobile || collapsed
|
|
721
|
+
if (mobile || collapsed) { panel.style.removeProperty('height'); return }
|
|
722
|
+
const max = Math.floor(maximum())
|
|
723
|
+
const value = Math.round(Math.max(min, Math.min(preferred, max)))
|
|
724
|
+
panel.style.height = `${value}px`
|
|
725
|
+
divider.setAttribute('aria-valuemin', String(min))
|
|
726
|
+
divider.setAttribute('aria-valuemax', String(max))
|
|
727
|
+
divider.setAttribute('aria-valuenow', String(value))
|
|
728
|
+
divider.setAttribute('aria-valuetext', `${value} pixels`)
|
|
729
|
+
}
|
|
730
|
+
const set = value => {
|
|
731
|
+
preferred = Math.max(min, Math.min(value, maximum()))
|
|
732
|
+
store.set(key, String(Math.round(preferred)))
|
|
733
|
+
apply()
|
|
734
|
+
}
|
|
735
|
+
const reset = () => { preferred = initial; store.clear(key); apply() }
|
|
736
|
+
const stop = () => {
|
|
737
|
+
if (!drag) return
|
|
738
|
+
const id = drag.id
|
|
739
|
+
drag = null
|
|
740
|
+
divider.removeAttribute('data-dragging')
|
|
741
|
+
document.body.removeAttribute('data-panel-resizing')
|
|
742
|
+
if (divider.hasPointerCapture?.(id)) divider.releasePointerCapture(id)
|
|
743
|
+
}
|
|
744
|
+
divider.addEventListener('pointerdown', event => {
|
|
745
|
+
if (event.button) return
|
|
746
|
+
event.preventDefault()
|
|
747
|
+
divider.focus({ preventScroll: true })
|
|
748
|
+
drag = { y: event.clientY, height: height(), id: event.pointerId }
|
|
749
|
+
divider.setPointerCapture(event.pointerId)
|
|
750
|
+
divider.setAttribute('data-dragging', '')
|
|
751
|
+
document.body.setAttribute('data-panel-resizing', '')
|
|
752
|
+
})
|
|
753
|
+
divider.addEventListener('pointermove', event => {
|
|
754
|
+
if (drag && event.pointerId === drag.id) set(drag.height + (event.clientY - drag.y) * (before ? -1 : 1))
|
|
755
|
+
})
|
|
756
|
+
for (const event of ['pointerup', 'pointercancel', 'lostpointercapture']) divider.addEventListener(event, stop)
|
|
757
|
+
divider.addEventListener('dblclick', reset)
|
|
758
|
+
divider.addEventListener('keydown', event => {
|
|
759
|
+
const delta = { ArrowUp: -10, ArrowDown: 10 }[event.key]
|
|
760
|
+
if (delta !== undefined) { event.preventDefault(); set(height() + delta * (before ? -1 : 1)) }
|
|
761
|
+
else if (['Home', 'End', 'Enter', ' '].includes(event.key)) {
|
|
762
|
+
event.preventDefault()
|
|
763
|
+
if (event.key === 'Home') set(min)
|
|
764
|
+
else if (event.key === 'End') set(maximum())
|
|
765
|
+
else reset()
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
panel.addEventListener('toggle', apply)
|
|
769
|
+
const observer = new ResizeObserver(apply)
|
|
770
|
+
observer.observe(container)
|
|
771
|
+
addEventListener('resize', apply)
|
|
772
|
+
const dispose = () => { stop(); observer.disconnect(); removeEventListener('resize', apply); panel.removeEventListener('toggle', apply); divider.remove() }
|
|
773
|
+
disposers.push(dispose)
|
|
774
|
+
apply()
|
|
775
|
+
return dispose
|
|
776
|
+
}
|
|
777
|
+
const composer = $('composer')
|
|
778
|
+
if (composer) addPanel(composer, { key: 'fleet:composer-height', label: 'Resize message composer', min: 130, initial: 170, before: true })
|
|
779
|
+
let board = null, disposeBoard = null
|
|
780
|
+
const syncBoard = () => {
|
|
781
|
+
const next = $('initiative-board')
|
|
782
|
+
if (next === board) return
|
|
783
|
+
disposeBoard?.()
|
|
784
|
+
board = next
|
|
785
|
+
if (board) {
|
|
786
|
+
disposeBoard = addPanel(board, { key: 'fleet:overview-height', label: 'Resize team overview', min: 90, initial: 220 })
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const mutation = new MutationObserver(syncBoard)
|
|
790
|
+
mutation.observe(container, { childList: true })
|
|
791
|
+
syncBoard()
|
|
792
|
+
panelLayoutCleanup = () => { mutation.disconnect(); disposers.forEach(dispose => dispose()) }
|
|
705
793
|
}
|
|
706
794
|
initSplitter()
|
|
707
795
|
|
package/public/control.js
CHANGED
|
@@ -112,6 +112,7 @@ function selectControl(session) {
|
|
|
112
112
|
const next=session?.managedId || null
|
|
113
113
|
if(next===controlId && next) return
|
|
114
114
|
controlId=next;controlSession=null;controlVersion++
|
|
115
|
+
window.Fleet.watchConversation(null)
|
|
115
116
|
$('control-panel').innerHTML=''
|
|
116
117
|
if(next){
|
|
117
118
|
$('control-panel').innerHTML=`<div class="conversation-header"><h3 id="conversation-title">Conversation</h3><button type="button" id="close-agent" class="button close-agent" title="Remove this conversation from Fleet">Close</button><label class="mode-picker"><span class="sr-only">Model for this agent</span><select id="model-choice" title="Applies from your next message"></select></label><label class="mode-picker"><span class="sr-only">Approvals for this agent</span><select id="approval-mode"><option value="auto">Auto approvals</option><option value="ask">Ask every time</option><option value="all">Approve everything</option></select></label><span id="agent-context" class="subtle context-chip"></span><span id="agent-state" class="subtle">Connecting…</span></div><div id="conversation" class="conversation" role="log" aria-label="Agent conversation" aria-live="off"><p class="note">Loading conversation…</p></div><div id="agent-error" class="form-error" role="status" hidden></div><div id="approvals"></div><form id="composer" class="composer"><label class="sr-only" for="message-input">Message this agent</label><ul id="slash-picker" class="slash-picker" role="listbox" aria-label="Commands and skills" hidden></ul><div id="attach-tray" class="attach-tray" hidden></div><textarea id="message-input" rows="3" maxlength="16000" placeholder="What should this agent do next? · press / for commands · paste an image" role="combobox" aria-expanded="false" aria-controls="slash-picker" aria-autocomplete="list"></textarea><div class="composer-footer"><span id="composer-hint" class="note">Enter to send · Shift + Enter for a new line</span><button id="stop-agent" type="button" class="button stop" hidden>■ Stop</button><button id="send-message" class="button resume" type="submit">Send ↗</button></div><p id="send-error" class="form-error" role="alert" hidden></p></form>`
|
package/public/styles.css
CHANGED
|
@@ -2592,3 +2592,54 @@ body[data-modal] { overflow:hidden }
|
|
|
2592
2592
|
.status-fleet { margin-left:0 }
|
|
2593
2593
|
.status-conn { border:0; padding-left:0 }
|
|
2594
2594
|
}
|
|
2595
|
+
|
|
2596
|
+
/* Open session sections. The transcript keeps the remaining vertical space. */
|
|
2597
|
+
.detail:has(#composer) #control-panel { padding:14px 16px 0; overflow:hidden }
|
|
2598
|
+
.detail:has(#composer) .conversation {
|
|
2599
|
+
border:0; border-radius:0; background:transparent; padding:0;
|
|
2600
|
+
resize:none; flex:1 1 0; height:auto; min-height:120px;
|
|
2601
|
+
}
|
|
2602
|
+
.conversation .block {
|
|
2603
|
+
border:0; border-radius:0; border-bottom:1px solid var(--line);
|
|
2604
|
+
background:transparent; margin:0; overflow:visible;
|
|
2605
|
+
}
|
|
2606
|
+
.conversation .block-head { border:0; background:var(--panel) }
|
|
2607
|
+
.conversation .block[data-status="running"] .block-icon { color:var(--term-yellow) }
|
|
2608
|
+
.conversation .block[data-status="error"] .block-icon { color:var(--term-red) }
|
|
2609
|
+
#initiative-board {
|
|
2610
|
+
border:0; border-radius:0; margin:0; background:transparent;
|
|
2611
|
+
min-height:0; flex:0 1 auto;
|
|
2612
|
+
}
|
|
2613
|
+
#initiative-board[open] { display:flex; flex-direction:column }
|
|
2614
|
+
#initiative-board>summary { flex:none; background:transparent; padding:10px 0 }
|
|
2615
|
+
#initiative-board .initiative-body { min-height:0; max-height:none; overflow:auto; padding:10px 0 }
|
|
2616
|
+
#initiative-board .initiative-handoff { border-radius:0; background:transparent; border-left:1px solid var(--line) }
|
|
2617
|
+
.detail:has(#composer) .composer {
|
|
2618
|
+
display:flex; flex-direction:column; min-height:130px; flex:0 1 auto;
|
|
2619
|
+
margin:0; padding:6px 0 12px;
|
|
2620
|
+
}
|
|
2621
|
+
.composer textarea {
|
|
2622
|
+
flex:1 1 0; min-height:50px; border:0; border-radius:0;
|
|
2623
|
+
background:transparent; resize:none; padding:10px 0;
|
|
2624
|
+
}
|
|
2625
|
+
.composer textarea:focus { outline:0; box-shadow:inset 2px 0 var(--accent); padding-left:10px }
|
|
2626
|
+
.composer-footer { flex:none }
|
|
2627
|
+
.panel-splitter {
|
|
2628
|
+
position:relative; height:9px; flex:0 0 9px; cursor:row-resize;
|
|
2629
|
+
touch-action:none; margin:0 -16px;
|
|
2630
|
+
}
|
|
2631
|
+
.panel-splitter::before {
|
|
2632
|
+
content:""; position:absolute; left:0; right:0; top:4px;
|
|
2633
|
+
height:1px; background:var(--line);
|
|
2634
|
+
}
|
|
2635
|
+
.panel-splitter:hover::before, .panel-splitter:focus-visible::before,
|
|
2636
|
+
.panel-splitter[data-dragging]::before { height:2px; background:var(--accent) }
|
|
2637
|
+
.panel-splitter:focus-visible { outline:0 }
|
|
2638
|
+
body[data-panel-resizing], body[data-panel-resizing] * { cursor:row-resize!important; user-select:none!important }
|
|
2639
|
+
@media(max-width:720px) {
|
|
2640
|
+
.detail:has(#composer) #control-panel { overflow:visible }
|
|
2641
|
+
.panel-splitter { display:none }
|
|
2642
|
+
#initiative-board .initiative-body { max-height:32vh }
|
|
2643
|
+
.detail:has(#composer) .conversation { height:460px; flex:none }
|
|
2644
|
+
.detail:has(#composer) .composer { height:auto; min-height:170px; border-top:1px solid var(--line) }
|
|
2645
|
+
}
|