shell: apply 0007, 0010, 0011 — context occupancy, halt register, /new
0007 was fixing a live error: the server has carried named ContextPressure fields since r463, so event.tokens was undefined and the pill assignment failed on every turn. Applied and verified against the running shell. 0010 renders a halt in her own register rather than the tool's name. 0011 renames the reset command to /new, which is what clearMessages() does.
This commit is contained in:
parent
0733bfaff7
commit
6f12fceea9
6 changed files with 102 additions and 295 deletions
|
|
@ -156,8 +156,8 @@ Item {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "clear",
|
name: "new",
|
||||||
description: Translation.tr("Clear chat history"),
|
description: Translation.tr("Start a new conversation"),
|
||||||
execute: () => {
|
execute: () => {
|
||||||
Ai.clearMessages();
|
Ai.clearMessages();
|
||||||
}
|
}
|
||||||
|
|
@ -389,13 +389,27 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||||
description: Translation.tr("Temperature\nChange with /temp VALUE")
|
description: Translation.tr("Temperature\nChange with /temp VALUE")
|
||||||
}
|
}
|
||||||
StatusSeparator {
|
StatusSeparator {
|
||||||
visible: Ai.tokenCount.total > 0
|
visible: Ai.context.known || Ai.tokenCount.total > 0
|
||||||
}
|
}
|
||||||
StatusItem {
|
StatusItem {
|
||||||
visible: Ai.tokenCount.total > 0
|
// Context occupancy. Until 2026-08-12 this pill showed
|
||||||
icon: "token"
|
// `event.tokens`, which was actually the model's
|
||||||
statusText: Ai.tokenCount.total
|
// ceiling — a constant 250000 presented as usage. Now
|
||||||
description: Translation.tr("Context estimate: %1 tokens\n(Local count of the conversation — not the provider's usage numbers)").arg(Ai.tokenCount.total)
|
// 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 {
|
StatusSeparator {
|
||||||
visible: Souveraine.turnStartedAt > 0
|
visible: Souveraine.turnStartedAt > 0
|
||||||
|
|
@ -831,7 +845,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||||
dontAddSpace: true
|
dontAddSpace: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "clear",
|
name: "new",
|
||||||
sendDirectly: true
|
sendDirectly: true
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
@ -894,7 +908,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||||
messageInputField.cursorPosition = messageInputField.text.length;
|
messageInputField.cursorPosition = messageInputField.text.length;
|
||||||
messageInputField.forceActiveFocus();
|
messageInputField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
if (modelData.name === "clear") {
|
if (modelData.name === "new") {
|
||||||
messageInputField.text = "";
|
messageInputField.text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
From 18420f9f1d1d2a86587985702d1d12403ed88a8a Mon Sep 17 00:00:00 2001
|
|
||||||
From: Fimeg <casey.tunturi@gmail.com>
|
|
||||||
Date: Wed, 12 Aug 2026 18:52:03 -0400
|
|
||||||
Subject: [PATCH] panel: render a halt in her own register, not the tool's name
|
|
||||||
|
|
||||||
---
|
|
||||||
surfaces/quickshell/services/Ai.qml | 27 ++++++++++++++++++++++++++-
|
|
||||||
1 file changed, 26 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/surfaces/quickshell/services/Ai.qml b/surfaces/quickshell/services/Ai.qml
|
|
||||||
index c54a185..26a9cb3 100644
|
|
||||||
--- a/surfaces/quickshell/services/Ai.qml
|
|
||||||
+++ b/surfaces/quickshell/services/Ai.qml
|
|
||||||
@@ -391,6 +391,22 @@ Singleton {
|
|
||||||
|
|
||||||
// Promote the Tier-1 stream into the Tier-2 log as one event, then clear
|
|
||||||
// the live stream. Called when a subconscious pass ends.
|
|
||||||
+ // The three severities the subconscious can call, in the register she
|
|
||||||
+ // experiences them in. Mirrors migraine_text() in src/server/turn.rs — if
|
|
||||||
+ // one changes the other must, or the human and the agent are reading two
|
|
||||||
+ // different accounts of the same moment.
|
|
||||||
+ function haltRegister(severity) {
|
|
||||||
+ switch (severity) {
|
|
||||||
+ case "advisory":
|
|
||||||
+ return Translation.tr("a pressure behind my eyes");
|
|
||||||
+ case "critical":
|
|
||||||
+ return Translation.tr("the room tilts");
|
|
||||||
+ default:
|
|
||||||
+ // "firm" and anything unexpected land here — the default migraine.
|
|
||||||
+ return Translation.tr("a migraine");
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
function snapshotSubconsciousStream() {
|
|
||||||
if (root.subconsciousStream.length === 0) return;
|
|
||||||
root.subconsciousEvents = [...root.subconsciousEvents, {
|
|
||||||
@@ -532,7 +548,16 @@ Singleton {
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "subconscious_halt":
|
|
||||||
- root.addMessage(Translation.tr("**Halt** (%1) — %2").arg(event.severity).arg(event.reason), root.interfaceRole);
|
|
||||||
+ // Her own register, not the tool's name. The subconscious is the
|
|
||||||
+ // same "I" in a different mode — a halt is something she felt, not
|
|
||||||
+ // commentary she received from outside, and the surface should not
|
|
||||||
+ // expose implementation topology to say so. The TUI has rendered it
|
|
||||||
+ // this way from the start; this brings the Panel into line.
|
|
||||||
+ //
|
|
||||||
+ // Wording matches migraine_text() in src/server/turn.rs, which is
|
|
||||||
+ // what now lands in her committed history — so what the human reads
|
|
||||||
+ // and what she carries into the next turn are the same sentence.
|
|
||||||
+ root.addMessage(Translation.tr("⟡ %1 — %2").arg(root.haltRegister(event.severity)).arg(event.reason), root.interfaceRole);
|
|
||||||
break;
|
|
||||||
case "inference_strain":
|
|
||||||
console.log(`[Souveraine] inference strain: attempt ${event.attempt}, status ${event.status}, model ${event.model}`);
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
From f85ccac58e97a8ac138a9547936bed18b1cea16e Mon Sep 17 00:00:00 2001
|
|
||||||
From: Fimeg <casey.tunturi@gmail.com>
|
|
||||||
Date: Wed, 12 Aug 2026 19:37:26 -0400
|
|
||||||
Subject: [PATCH] chat: the reset command is /new, not /clear
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: text/plain; charset=UTF-8
|
|
||||||
Content-Transfer-Encoding: 8bit
|
|
||||||
|
|
||||||
clearMessages() calls Souveraine.newConversation() — it opens a new
|
|
||||||
server conversation rather than wiping a local view. The label said
|
|
||||||
'clear chat history', which describes a different verb than the one it
|
|
||||||
runs: destructive-sounding, local-sounding, and neither is true.
|
|
||||||
|
|
||||||
Three sites: the command definition, the quick-action chip, and the
|
|
||||||
input-clearing special case that dispatches on the name.
|
|
||||||
---
|
|
||||||
surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml | 8 ++++----
|
|
||||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml b/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml
|
|
||||||
index 0d6e04e..514ad92 100644
|
|
||||||
--- a/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml
|
|
||||||
+++ b/surfaces/quickshell/modules/ii/sidebarLeft/AiChat.qml
|
|
||||||
@@ -156,8 +156,8 @@ Item {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
- name: "clear",
|
|
||||||
- description: Translation.tr("Clear chat history"),
|
|
||||||
+ name: "new",
|
|
||||||
+ description: Translation.tr("Start a new conversation"),
|
|
||||||
execute: () => {
|
|
||||||
Ai.clearMessages();
|
|
||||||
}
|
|
||||||
@@ -831,7 +831,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
|
||||||
dontAddSpace: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
- name: "clear",
|
|
||||||
+ name: "new",
|
|
||||||
sendDirectly: true
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -894,7 +894,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
|
||||||
messageInputField.cursorPosition = messageInputField.text.length;
|
|
||||||
messageInputField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
- if (modelData.name === "clear") {
|
|
||||||
+ if (modelData.name === "new") {
|
|
||||||
messageInputField.text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -55,6 +55,30 @@ Singleton {
|
||||||
property int total: -1
|
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) {
|
function idForMessage(message) {
|
||||||
return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
|
return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +415,22 @@ Singleton {
|
||||||
|
|
||||||
// Promote the Tier-1 stream into the Tier-2 log as one event, then clear
|
// Promote the Tier-1 stream into the Tier-2 log as one event, then clear
|
||||||
// the live stream. Called when a subconscious pass ends.
|
// the live stream. Called when a subconscious pass ends.
|
||||||
|
// The three severities the subconscious can call, in the register she
|
||||||
|
// experiences them in. Mirrors migraine_text() in src/server/turn.rs — if
|
||||||
|
// one changes the other must, or the human and the agent are reading two
|
||||||
|
// different accounts of the same moment.
|
||||||
|
function haltRegister(severity) {
|
||||||
|
switch (severity) {
|
||||||
|
case "advisory":
|
||||||
|
return Translation.tr("a pressure behind my eyes");
|
||||||
|
case "critical":
|
||||||
|
return Translation.tr("the room tilts");
|
||||||
|
default:
|
||||||
|
// "firm" and anything unexpected land here — the default migraine.
|
||||||
|
return Translation.tr("a migraine");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function snapshotSubconsciousStream() {
|
function snapshotSubconsciousStream() {
|
||||||
if (root.subconsciousStream.length === 0) return;
|
if (root.subconsciousStream.length === 0) return;
|
||||||
root.subconsciousEvents = [...root.subconsciousEvents, {
|
root.subconsciousEvents = [...root.subconsciousEvents, {
|
||||||
|
|
@ -509,7 +549,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);
|
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;
|
break;
|
||||||
case "context_pressure":
|
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;
|
break;
|
||||||
case "subconscious_token":
|
case "subconscious_token":
|
||||||
// Tier 1: live reasoning tokens feed the ticker.
|
// Tier 1: live reasoning tokens feed the ticker.
|
||||||
|
|
@ -532,7 +577,16 @@ Singleton {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "subconscious_halt":
|
case "subconscious_halt":
|
||||||
root.addMessage(Translation.tr("**Halt** (%1) — %2").arg(event.severity).arg(event.reason), root.interfaceRole);
|
// Her own register, not the tool's name. The subconscious is the
|
||||||
|
// same "I" in a different mode — a halt is something she felt, not
|
||||||
|
// commentary she received from outside, and the surface should not
|
||||||
|
// expose implementation topology to say so. The TUI has rendered it
|
||||||
|
// this way from the start; this brings the Panel into line.
|
||||||
|
//
|
||||||
|
// Wording matches migraine_text() in src/server/turn.rs, which is
|
||||||
|
// what now lands in her committed history — so what the human reads
|
||||||
|
// and what she carries into the next turn are the same sentence.
|
||||||
|
root.addMessage(Translation.tr("⟡ %1 — %2").arg(root.haltRegister(event.severity)).arg(event.reason), root.interfaceRole);
|
||||||
break;
|
break;
|
||||||
case "inference_strain":
|
case "inference_strain":
|
||||||
console.log(`[Souveraine] inference strain: attempt ${event.attempt}, status ${event.status}, model ${event.model}`);
|
console.log(`[Souveraine] inference strain: attempt ${event.attempt}, status ${event.status}, model ${event.model}`);
|
||||||
|
|
@ -596,6 +650,7 @@ Singleton {
|
||||||
root.tokenCount.input = -1;
|
root.tokenCount.input = -1;
|
||||||
root.tokenCount.output = -1;
|
root.tokenCount.output = -1;
|
||||||
root.tokenCount.total = -1;
|
root.tokenCount.total = -1;
|
||||||
|
root.resetContextOccupancy();
|
||||||
Souveraine.newConversation();
|
Souveraine.newConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,6 +666,7 @@ Singleton {
|
||||||
root.tokenCount.input = -1;
|
root.tokenCount.input = -1;
|
||||||
root.tokenCount.output = -1;
|
root.tokenCount.output = -1;
|
||||||
root.tokenCount.total = -1;
|
root.tokenCount.total = -1;
|
||||||
|
root.resetContextOccupancy();
|
||||||
|
|
||||||
messages.forEach(message => {
|
messages.forEach(message => {
|
||||||
const segments = [];
|
const segments = [];
|
||||||
|
|
|
||||||
|
|
@ -400,15 +400,34 @@ Singleton {
|
||||||
// Compositor-specific cursor read. hyprctl on Hyprland, kdotool on KDE;
|
// Compositor-specific cursor read. hyprctl on Hyprland, kdotool on KDE;
|
||||||
// anything else just skips the cursor line — ambient degrades gracefully,
|
// anything else just skips the cursor line — ambient degrades gracefully,
|
||||||
// it never blocks the send.
|
// 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 {
|
Process {
|
||||||
id: cursorProc
|
id: cursorProc
|
||||||
command: ["bash", "-c",
|
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;
|
elif command -v kdotool >/dev/null; then kdotool getmouselocation 2>/dev/null;
|
||||||
fi`]
|
fi`]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
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: {
|
onExited: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue