panel: a message typed mid-turn reaches her
Souveraine.send returns false for a busy turn exactly as it does for a dead
server, and the sidebar rendered every false as "server unreachable" — so
typing while she worked showed the words, then a lie, and dropped the text.
The verbs existed on both sides since 93fe30a and nothing called them.
Type-while-busy now interjects, as the TUI has always done
(src/ui/app/mod.rs:1006). Esc with nothing attached raises a hand. A queued
count sits in the status row and says sent, not read, because 202 from
/interject means only that it was taken.
Deliberately unchanged: the two drain sites and their system/user registers.
Providers disagree about which one they want and that choice is not ready to
be made. Latency is unchanged too — turn.rs:600 runs a whole tool round
without looking, so a note sent into a fifteen-call round waits for it.
This commit is contained in:
parent
b3bbfeeac4
commit
da8d69af2f
2 changed files with 59 additions and 2 deletions
|
|
@ -425,6 +425,15 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||||
? Translation.tr("Waiting on the agent\nSent at %1").arg(root.formatClockTime(Souveraine.turnStartedAt))
|
? Translation.tr("Waiting on the agent\nSent at %1").arg(root.formatClockTime(Souveraine.turnStartedAt))
|
||||||
: Translation.tr("Last turn took %1\nSent at %2").arg(root.formatTurnDuration(Souveraine.turnElapsedMs)).arg(root.formatClockTime(Souveraine.turnStartedAt))
|
: Translation.tr("Last turn took %1\nSent at %2").arg(root.formatTurnDuration(Souveraine.turnElapsedMs)).arg(root.formatClockTime(Souveraine.turnStartedAt))
|
||||||
}
|
}
|
||||||
|
StatusSeparator {
|
||||||
|
visible: Ai.queuedInterjections > 0
|
||||||
|
}
|
||||||
|
StatusItem {
|
||||||
|
visible: Ai.queuedInterjections > 0
|
||||||
|
icon: "pan_tool"
|
||||||
|
statusText: Translation.tr("%1 queued").arg(Ai.queuedInterjections)
|
||||||
|
description: Translation.tr("Notes handed to the turn in flight.\nShe reads them at the next round boundary — a long tool round finishes first.\nQueued means sent, not yet read.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -790,10 +799,19 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||||
}
|
}
|
||||||
event.accepted = false; // No image, let text pasting proceed
|
event.accepted = false; // No image, let text pasting proceed
|
||||||
} else if (event.key === Qt.Key_Escape) {
|
} else if (event.key === Qt.Key_Escape) {
|
||||||
// Esc to detach file
|
// Esc detaches a file first — that is the
|
||||||
|
// older, narrower meaning and it keeps it.
|
||||||
if (Ai.pendingFilePath.length > 0) {
|
if (Ai.pendingFilePath.length > 0) {
|
||||||
Ai.attachFile("");
|
Ai.attachFile("");
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
} else if (Souveraine.turnActive) {
|
||||||
|
// Nothing attached and a turn is running:
|
||||||
|
// Esc is the raised hand. Cancellation is
|
||||||
|
// read at a round boundary, not mid-tool
|
||||||
|
// (`src/server/turn.rs:248`), so partial
|
||||||
|
// work survives.
|
||||||
|
Souveraine.cancelTurn();
|
||||||
|
event.accepted = true;
|
||||||
} else {
|
} else {
|
||||||
event.accepted = false;
|
event.accepted = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,13 @@ Singleton {
|
||||||
property Component aiModelComponent: AiModel {}
|
property Component aiModelComponent: AiModel {}
|
||||||
readonly property string interfaceRole: "interface"
|
readonly property string interfaceRole: "interface"
|
||||||
|
|
||||||
|
// Notes handed to the turn already running, still waiting to be read.
|
||||||
|
// The server takes them at a round boundary (`src/server/turn.rs:260`
|
||||||
|
// and `:453`), and 202 from `/interject` means *queued*, never *read* —
|
||||||
|
// so this counts what was sent, not what landed. Cleared when the turn
|
||||||
|
// ends, by which point the drain has run.
|
||||||
|
property int queuedInterjections: 0
|
||||||
|
|
||||||
signal responseFinished()
|
signal responseFinished()
|
||||||
|
|
||||||
property var messageIDs: []
|
property var messageIDs: []
|
||||||
|
|
@ -121,6 +128,10 @@ Singleton {
|
||||||
Connections {
|
Connections {
|
||||||
target: Souveraine
|
target: Souveraine
|
||||||
|
|
||||||
|
function onTurnActiveChanged() {
|
||||||
|
if (!Souveraine.turnActive) root.queuedInterjections = 0;
|
||||||
|
}
|
||||||
|
|
||||||
function onAgentsRefreshed() {
|
function onAgentsRefreshed() {
|
||||||
const map = {};
|
const map = {};
|
||||||
Souveraine.agentList.forEach(id => {
|
Souveraine.agentList.forEach(id => {
|
||||||
|
|
@ -717,6 +728,18 @@ Singleton {
|
||||||
|
|
||||||
function sendUserMessage(message) {
|
function sendUserMessage(message) {
|
||||||
if (message.length === 0) return;
|
if (message.length === 0) return;
|
||||||
|
|
||||||
|
// A turn is already running: this is an interjection, not a new turn.
|
||||||
|
// The TUI has always worked this way — `src/ui/app/mod.rs:1006`,
|
||||||
|
// "Submit always — when busy, this becomes an interjection". The
|
||||||
|
// Panel instead dropped the text on the floor and reported the server
|
||||||
|
// unreachable, because `Souveraine.send` returns false for a busy turn
|
||||||
|
// exactly as it does for a dead server.
|
||||||
|
if (Souveraine.turnActive) {
|
||||||
|
root.interjectUserMessage(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
root.addMessage(message, "user");
|
root.addMessage(message, "user");
|
||||||
const result = Souveraine.send(message);
|
const result = Souveraine.send(message);
|
||||||
if (result === "step-up") {
|
if (result === "step-up") {
|
||||||
|
|
@ -740,12 +763,28 @@ Singleton {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!result) {
|
if (!result) {
|
||||||
root.addMessage(Translation.tr("Souveraine server unreachable at %1 — start it with `souveraine server`").arg(Souveraine.serverBase), root.interfaceRole);
|
root.addMessage(Souveraine.serverUp
|
||||||
|
? Translation.tr("No agent selected — pick one before sending.")
|
||||||
|
: Translation.tr("Souveraine server unreachable at %1 — start it with `souveraine server`").arg(Souveraine.serverBase),
|
||||||
|
root.interfaceRole);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
root._startStreaming();
|
root._startStreaming();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Slip a note into the turn already in flight.
|
||||||
|
|
||||||
|
It is read at the next round boundary, so a note sent during a long
|
||||||
|
tool round waits for that round to finish — `src/server/turn.rs:600`
|
||||||
|
runs every tool in a round without checking. That latency is real and
|
||||||
|
is not something this function can fix. */
|
||||||
|
function interjectUserMessage(message) {
|
||||||
|
if (message.length === 0) return;
|
||||||
|
root.addMessage(message, "user");
|
||||||
|
root.queuedInterjections = root.queuedInterjections + 1;
|
||||||
|
Souveraine.interject(message);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the streaming assistant message container. Called after a
|
// Set up the streaming assistant message container. Called after a
|
||||||
// successful send (or after a step-up auth retry succeeds).
|
// successful send (or after a step-up auth retry succeeds).
|
||||||
function _startStreaming() {
|
function _startStreaming() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue