import QtQuick import QtQuick.Controls import QtQuick.Layouts // What the engine is set to: where it runs, how it frames itself, when it // asks, whether it is planning. Everything here writes straight into the // engine's config (AgentCore), never into a copy the engine cannot see. // // The panel scrolls (a phone-sized window cannot show it all) and every // control keeps a finger-sized hit area. Rectangle { id: side property string workspace: "" property string provider: "" property string model: "" property string mode: "coding" property int approval: 1 // cst_approval_t property bool planMode: false property bool running: false property string status: "" property string todo: "" property var sessions: [] // what the store holds (AgentCore::sessions) property string sessionId: "" signal modeRequested(string mode) signal approvalRequested(int approval) signal planModeRequested(bool on) signal sessionRequested(string id) signal sessionNewRequested() signal sessionDeleteRequested(string id) readonly property alias contentColumn: col color: Theme.surface border.width: 1 border.color: Theme.border implicitHeight: col.implicitHeight + Theme.pad * 2 // One selectable row of a segmented control. component Seg: Button { id: seg property bool current: false flat: true implicitHeight: Theme.segmentHeight padding: 0 background: Rectangle { radius: Theme.radiusSm color: seg.current ? Theme.accentDim : (seg.hovered ? Theme.surfaceAlt : "transparent") border.width: 1 border.color: seg.current ? Theme.accent : Theme.border } contentItem: Text { text: seg.text color: seg.current ? Theme.text : Theme.textDim font.pixelSize: Theme.smallSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } Flickable { anchors.fill: parent contentWidth: width contentHeight: col.implicitHeight + Theme.pad * 2 clip: true boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: ScrollBar {} ColumnLayout { id: col x: Theme.pad y: Theme.pad width: parent.width - Theme.pad * 2 spacing: Theme.gapL // --- where --- ColumnLayout { Layout.fillWidth: true spacing: 4 RowLayout { spacing: Theme.gap Rectangle { width: 8; height: 8; radius: 4; color: Theme.accent } Text { text: "CST-agent" color: Theme.text font.pixelSize: Theme.bodySize font.bold: true } Item { Layout.fillWidth: true } Text { visible: side.running text: "turn running" color: Theme.warn font.pixelSize: Theme.smallSize } } Text { Layout.fillWidth: true text: side.workspace.length ? side.workspace : "(no workspace)" color: Theme.textDim font.pixelSize: Theme.smallSize elide: Text.ElideMiddle ToolTip.visible: wsHover.hovered ToolTip.text: side.workspace HoverHandler { id: wsHover } } Text { Layout.fillWidth: true visible: side.provider.length > 0 || side.model.length > 0 text: (side.provider.length ? side.provider : "?") + (side.model.length ? " · " + side.model : "") color: Theme.textFaint font.pixelSize: Theme.smallSize elide: Text.ElideRight } Text { Layout.fillWidth: true visible: side.sessionId.length > 0 text: "session " + side.sessionId color: Theme.textFaint font.pixelSize: Theme.smallSize elide: Text.ElideMiddle } Text { Layout.fillWidth: true visible: side.status.length > 0 text: side.status color: Theme.textFaint font.pixelSize: Theme.smallSize elide: Text.ElideRight } } // --- which conversation --- ColumnLayout { Layout.fillWidth: true spacing: 4 RowLayout { Layout.fillWidth: true spacing: Theme.gap Text { text: "Sessions" color: Theme.textDim font.pixelSize: Theme.smallSize font.bold: true } Item { Layout.fillWidth: true } // Starting a session while a turn runs would move the // engine out from under it: the whole block is off then. ToolButton { id: newSession objectName: "newSessionButton" implicitWidth: 68 implicitHeight: Theme.segmentHeight enabled: !side.running ToolTip.visible: hovered ToolTip.text: "Start a new session" contentItem: RowLayout { spacing: 4 ThemeIcon { name: "list-add" glyph: "+" size: 14 color: newSession.enabled ? (newSession.hovered ? Theme.text : Theme.textDim) : Theme.textFaint } Text { text: "new" color: newSession.enabled ? (newSession.hovered ? Theme.text : Theme.textDim) : Theme.textFaint font.pixelSize: Theme.smallSize } } background: Rectangle { radius: Theme.radiusSm color: parent.hovered && parent.enabled ? Theme.surfaceHi : "transparent" border.width: 1 border.color: Theme.border } onClicked: side.sessionNewRequested() } } Repeater { model: side.sessions delegate: SessionRow { Layout.fillWidth: true entry: modelData onActivated: function (id) { side.sessionRequested(id) } onRemoveRequested: function (id) { side.sessionDeleteRequested(id) } } } Text { Layout.fillWidth: true visible: side.sessions.length === 0 text: "nothing stored here yet" color: Theme.textFaint font.pixelSize: Theme.smallSize wrapMode: Text.Wrap } } // --- how it frames itself --- ColumnLayout { Layout.fillWidth: true spacing: 4 Text { text: "Mode"; color: Theme.textDim; font.pixelSize: Theme.smallSize; font.bold: true } RowLayout { Layout.fillWidth: true spacing: 4 Seg { objectName: "modeCoding" Layout.fillWidth: true text: "coding" current: side.mode !== "general" onClicked: side.modeRequested("coding") } Seg { objectName: "modeGeneral" Layout.fillWidth: true text: "general" current: side.mode === "general" onClicked: side.modeRequested("general") } } Text { Layout.fillWidth: true text: side.mode === "general" ? "documents, data, everyday files" : "code framed: repos, builds, tests" color: Theme.textFaint font.pixelSize: Theme.smallSize wrapMode: Text.Wrap } } // --- when it asks --- ColumnLayout { Layout.fillWidth: true spacing: 4 Text { text: "Approval"; color: Theme.textDim; font.pixelSize: Theme.smallSize; font.bold: true } RowLayout { Layout.fillWidth: true spacing: 4 Seg { Layout.fillWidth: true text: "ask" current: side.approval === 0 onClicked: side.approvalRequested(0) } Seg { Layout.fillWidth: true text: "auto" current: side.approval === 1 onClicked: side.approvalRequested(1) } Seg { Layout.fillWidth: true text: "yolo" current: side.approval === 2 onClicked: side.approvalRequested(2) } } Text { Layout.fillWidth: true text: side.approval === 0 ? "every tool asks first" : side.approval === 2 ? "never asks — writes go through" : "sandbox and rules decide, asks on doubt" color: Theme.textFaint font.pixelSize: Theme.smallSize wrapMode: Text.Wrap } RowLayout { Layout.fillWidth: true Layout.topMargin: 2 spacing: Theme.gap Switch { id: planSwitch objectName: "planSwitch" checked: side.planMode implicitHeight: Theme.touchTarget onClicked: side.planModeRequested(checked) /* The Basic style's indicator is a dark pill with a dark * knob here, so it is drawn: thumb-sized, accent when * on, clearly off when not. */ indicator: Rectangle { implicitWidth: 44 implicitHeight: 22 x: planSwitch.leftPadding y: parent.height / 2 - height / 2 radius: 11 color: planSwitch.checked ? Theme.accentDim : Theme.surfaceAlt border.width: 1 border.color: planSwitch.checked ? Theme.accent : Theme.border Rectangle { width: 16 height: 16 y: 2 x: planSwitch.checked ? parent.width - width - 3 : 3 radius: 8 color: planSwitch.checked ? Theme.text : Theme.textDim Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } } } } } Text { text: "Plan mode" color: Theme.textDim font.pixelSize: Theme.smallSize } Item { Layout.fillWidth: true } } Text { Layout.fillWidth: true visible: side.planMode text: "read-only tools and a planning note" color: Theme.textFaint font.pixelSize: Theme.smallSize wrapMode: Text.Wrap } } // --- the plan the engine keeps --- TodoPanel { Layout.fillWidth: true text: side.todo } Text { Layout.fillWidth: true Layout.topMargin: Theme.gap text: "engine in-process · v" + Qt.application.version color: Theme.textFaint font.pixelSize: Theme.smallSize elide: Text.ElideRight } } } }