Watch
1
0
Fork
You've already forked souveraine
0

patches: queue 0007, render context occupancy and stop reading hyprctl errors as cursor

Requires f266136 in the running server; without it the pill honestly shows
an em dash rather than a wrong number. Unapplied, unloaded.
This commit is contained in:
Fimeg 2026-08-12 12:20:11 -04:00
commit 5a20307ddd

View file

@ -0,0 +1,170 @@
From 9035751d30a9d1d364faaaa6b38b386353ca0f6c Mon Sep 17 00:00:00 2001
From: Souveraine <souveraine@wiuf.net>
Date: Wed, 12 Aug 2026 12:11:04 -0400
Subject: [PATCH] shell: render context occupancy, and stop reading hyprctl
errors as a cursor
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The context pill showed event.tokens, which carried the model's ceiling
rather than the usage — a constant 250000 that looked like a measurement.
The wire now carries tokens_used and context_limit separately; render both
and the ratio, with archivist threshold shading at 70/85%.
The ambient cursor probe tested whether hyprctl was installed, not whether
it answered. Under viewtop the binary exists but there is no Hyprland, so
'HYPRLAND_INSTANCE_SIGNATURE not set!' was collected as the cursor position
and shipped in every ambient block. Validate the reply's shape instead.
---
.../modules/ii/sidebarLeft/AiChat.qml | 24 +++++++++++---
surfaces/quickshell/services/Ai.qml | 33 ++++++++++++++++++-
surfaces/quickshell/services/Souveraine.qml | 23 +++++++++++--
3 files changed, 72 insertions(+), 8 deletions(-)
diff --git a/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml b/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml
index bba12a1..f3ed1b7 100644
--- a/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml
+++ b/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml
@@ -388,13 +388,27 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
description: Translation.tr("Temperature\nChange with /temp VALUE")
}
StatusSeparator {
- visible: Ai.tokenCount.total > 0
+ visible: Ai.context.known || Ai.tokenCount.total > 0
}
StatusItem {
- visible: Ai.tokenCount.total > 0
- icon: "token"
- statusText: Ai.tokenCount.total
- description: Translation.tr("Context estimate: %1 tokens\n(Local count of the conversation — not the provider's usage numbers)").arg(Ai.tokenCount.total)
+ // Context occupancy. Until 2026-08-12 this pill showed
+ // `event.tokens`, which was actually the model's
+ // ceiling — a constant 250000 presented as usage. Now
+ // it shows both numbers and the ratio between them,
+ // which is the only reading that means anything.
+ visible: Ai.context.known || Ai.tokenCount.total > 0
+ icon: Ai.context.percent >= 85 ? "error"
+ : Ai.context.percent >= 70 ? "warning"
+ : "token"
+ statusText: Ai.context.known
+ ? `${Math.round(Ai.context.used / 1000)}k/${Math.round(Ai.context.limit / 1000)}k · ${Ai.context.percent}%`
+ : (Ai.tokenCount.total > 0 ? `${Ai.tokenCount.total}` : "—")
+ description: Ai.context.known
+ ? Translation.tr("Context: %1 of %2 tokens (%3%)\nCeiling comes from this agent's configured context window.\nArchivist begins compacting around 70%.")
+ .arg(Ai.context.used.toLocaleString())
+ .arg(Ai.context.limit.toLocaleString())
+ .arg(Ai.context.percent)
+ : Translation.tr("Context occupancy unknown until the next turn reports it.")
}
StatusSeparator {
visible: Souveraine.turnStartedAt > 0
diff --git a/surfaces/quickshell/services/Ai.qml b/surfaces/quickshell/services/Ai.qml
index c54a185..bc1779b 100644
--- a/surfaces/quickshell/services/Ai.qml
+++ b/surfaces/quickshell/services/Ai.qml
@@ -55,6 +55,30 @@ Singleton {
property int total: -1
}
+ // Live context occupancy, from the server's `context_pressure` event.
+ //
+ // These are three different numbers and used to be two. Until 2026-08-12
+ // the wire carried `(pressure, context_limit)` positionally and this layer
+ // read the ceiling as `event.tokens` — so the panel displayed a constant
+ // 250000 and called it usage. `pressure` was discarded entirely; nothing
+ // in the shell held it. Keep all three, and keep them named.
+ property QtObject context: QtObject {
+ property real pressure: -1 // 0..1, or -1 when unknown
+ property int used: -1 // tokens occupied
+ property int limit: -1 // the model's ceiling for this agent
+ readonly property bool known: limit > 0 && used >= 0
+ readonly property int percent: known ? Math.round((used / limit) * 100) : -1
+ }
+
+ // Context occupancy is a property of the conversation, not of a turn:
+ // reset it only when the conversation itself changes. -1 means "unknown",
+ // which is an honest state and renders as "—" rather than as zero.
+ function resetContextOccupancy() {
+ root.context.pressure = -1;
+ root.context.used = -1;
+ root.context.limit = -1;
+ }
+
function idForMessage(message) {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
}
@@ -509,7 +533,12 @@ Singleton {
root.addMessage(Translation.tr("**Context pressure** — tier %1, %2% full. She can feel the walls.").arg(event.tier).arg(Math.round(event.pressure * 100)), root.interfaceRole);
break;
case "context_pressure":
- root.tokenCount.total = event.tokens;
+ // `tokens_used` and `context_limit` are distinct fields on the
+ // wire as of 2026-08-12. The old `event.tokens` was the ceiling.
+ root.context.pressure = event.pressure ?? -1;
+ root.context.used = event.tokens_used ?? -1;
+ root.context.limit = event.context_limit ?? -1;
+ root.tokenCount.total = event.tokens_used ?? -1;
break;
case "subconscious_token":
// Tier 1: live reasoning tokens feed the ticker.
@@ -596,6 +625,7 @@ Singleton {
root.tokenCount.input = -1;
root.tokenCount.output = -1;
root.tokenCount.total = -1;
+ root.resetContextOccupancy();
Souveraine.newConversation();
}
@@ -611,6 +641,7 @@ Singleton {
root.tokenCount.input = -1;
root.tokenCount.output = -1;
root.tokenCount.total = -1;
+ root.resetContextOccupancy();
messages.forEach(message => {
const segments = [];
diff --git a/surfaces/quickshell/services/Souveraine.qml b/surfaces/quickshell/services/Souveraine.qml
index 0898ea8..9b57702 100644
--- a/surfaces/quickshell/services/Souveraine.qml
+++ b/surfaces/quickshell/services/Souveraine.qml
@@ -400,15 +400,34 @@ Singleton {
// Compositor-specific cursor read. hyprctl on Hyprland, kdotool on KDE;
// anything else just skips the cursor line — ambient degrades gracefully,
// it never blocks the send.
+ //
+ // 2026-08-12: `command -v hyprctl` tests whether the tool is *installed*,
+ // not whether it *answered*. On the phone hyprctl is present (a Lua-eval
+ // shim) but there is no Hyprland under viewtop, so it printed
+ // "HYPRLAND_INSTANCE_SIGNATURE not set!" and StdioCollector stored that
+ // sentence as the cursor position — shipped in every ambient block, every
+ // turn. Validate the shape of the reply; an answer that isn't a
+ // coordinate pair is not an answer.
Process {
id: cursorProc
command: ["bash", "-c",
- `if command -v hyprctl >/dev/null; then hyprctl cursorpos;
+ `if command -v hyprctl >/dev/null; then hyprctl cursorpos 2>/dev/null;
elif command -v kdotool >/dev/null; then kdotool getmouselocation 2>/dev/null;
fi`]
stdout: StdioCollector {
onStreamFinished: {
- root._cursorPos = text.trim();
+ const reply = text.trim();
+ // "1234, 567" from hyprctl; kdotool's x:N y:N is normalised
+ // by the caller below. Anything else is discarded.
+ const pair = reply.match(/^(-?\d+)\s*,\s*(-?\d+)$/);
+ const kde = reply.match(/x:\s*(-?\d+)\s+y:\s*(-?\d+)/);
+ if (pair) {
+ root._cursorPos = `${pair[1]}, ${pair[2]}`;
+ } else if (kde) {
+ root._cursorPos = `${kde[1]}, ${kde[2]}`;
+ } else {
+ root._cursorPos = "";
+ }
}
}
onExited: {
--
2.55.0