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:
parent
e784fe48a3
commit
5a20307ddd
1 changed files with 170 additions and 0 deletions
|
|
@ -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
|
||||||
|
|
||||||
Loading…
Reference in a new issue