shell: offer resume instead of forcing it
This commit is contained in:
parent
b37b1d396a
commit
ec87e6a9dc
2 changed files with 58 additions and 132 deletions
|
|
@ -44,17 +44,36 @@ Singleton {
|
|||
property var agents: ({})
|
||||
property var agentList: Object.keys(agents)
|
||||
property string currentAgentId: ""
|
||||
// Auto-resume: whenever the active agent is (re)established — shell start,
|
||||
// reboot, agent switch — re-attach her latest server-persisted conversation
|
||||
// so the next message continues the thread instead of minting a fresh one.
|
||||
// Without this every shell reload starts amnesiac, and the shell reloads
|
||||
// often (a deploy, a lock, a crash). The server is the source of truth here;
|
||||
// the surface deliberately keeps no agent → conversation map of its own.
|
||||
// On agent (re)establishment — shell start, reboot, agent switch — we look
|
||||
// up her latest server-persisted conversation. What we do with it depends
|
||||
// on autoResume.
|
||||
//
|
||||
// autoResume false (default): we *offer* it. `resumeOffered` fires with
|
||||
// the thread's id and metadata; nothing is attached. Doing nothing
|
||||
// starts fresh, which is what a reload should do. Continuing is one
|
||||
// deliberate act (acceptOfferedResume), not a default.
|
||||
// autoResume true: legacy behaviour — attach it silently.
|
||||
//
|
||||
// The distinction matters because the shell reloads often (a deploy, a
|
||||
// lock, a crash) and silent re-attachment makes every one of those look
|
||||
// like a continuation of a conversation the human may have finished with.
|
||||
// Explicit resume paths (/resume, the Face control) are unaffected: they
|
||||
// call resumeLatestConversation() with no argument and still load.
|
||||
//
|
||||
// Note this is NOT the amnesia fix from e4e6594 — that one stopped
|
||||
// selectAgent clearing conversationId on an unchanged agent, and stays.
|
||||
// Gated so it never clobbers a live turn or an already-attached thread.
|
||||
property bool autoResume: Config.options?.ai?.autoResume ?? false
|
||||
|
||||
// Emitted when a resumable thread exists and we chose not to attach it.
|
||||
// agentId/conversationId identify it; the rest is for drawing the offer.
|
||||
signal resumeOffered(string agentId, string conversationId, string title, string updatedAt)
|
||||
property string offeredConversationId: ""
|
||||
|
||||
onCurrentAgentIdChanged: {
|
||||
if (root.currentAgentId.length > 0 && root.serverUp
|
||||
&& root.conversationId.length === 0 && !root.turnActive) {
|
||||
root.resumeLatestConversation();
|
||||
root.resumeLatestConversation(!root.autoResume);
|
||||
}
|
||||
}
|
||||
property string conversationId: ""
|
||||
|
|
@ -214,6 +233,9 @@ Singleton {
|
|||
Process {
|
||||
id: listConversations
|
||||
property string agentId: ""
|
||||
// Set by resumeLatestConversation(true): announce the thread rather
|
||||
// than loading it.
|
||||
property bool offerOnly: false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (listConversations.agentId !== root.currentAgentId) return;
|
||||
|
|
@ -240,7 +262,16 @@ Singleton {
|
|||
return;
|
||||
}
|
||||
// Server sorts by updated_at descending — [0] is her latest.
|
||||
root._loadConversation(listConversations.agentId, conversations[0].id);
|
||||
const latest = conversations[0];
|
||||
if (listConversations.offerOnly) {
|
||||
// Announce, don't attach. conversationId stays empty, so a
|
||||
// send without accepting mints a fresh thread on purpose.
|
||||
root.offeredConversationId = latest.id;
|
||||
root.resumeOffered(listConversations.agentId, latest.id,
|
||||
latest.title ?? "", latest.updated_at ?? "");
|
||||
return;
|
||||
}
|
||||
root._loadConversation(listConversations.agentId, latest.id);
|
||||
}
|
||||
}
|
||||
onExited: exitCode => {
|
||||
|
|
@ -292,8 +323,11 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
function resumeLatestConversation() {
|
||||
// offerOnly: fetch the latest thread but announce it instead of attaching.
|
||||
// Defaults to false so every existing caller keeps its old behaviour.
|
||||
function resumeLatestConversation(offerOnly) {
|
||||
if (!root.serverUp || root.currentAgentId.length === 0 || root.turnActive) return false;
|
||||
listConversations.offerOnly = (offerOnly === true);
|
||||
listConversations.agentId = root.currentAgentId;
|
||||
listConversations.command = [
|
||||
"curl", "-sf", "--max-time", "5",
|
||||
|
|
@ -303,6 +337,21 @@ Singleton {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Take up an offer made by resumeOffered. No-op if the offer has gone
|
||||
// stale (a turn started, or something else attached in the meantime).
|
||||
function acceptOfferedResume() {
|
||||
if (root.offeredConversationId.length === 0 || root.turnActive) return false;
|
||||
if (root.conversationId.length > 0) return false;
|
||||
root._loadConversation(root.currentAgentId, root.offeredConversationId);
|
||||
root.offeredConversationId = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decline. The thread stays on the server; we simply start fresh.
|
||||
function dismissOfferedResume() {
|
||||
root.offeredConversationId = "";
|
||||
}
|
||||
|
||||
function _loadConversation(agentId, conversationId) {
|
||||
loadConversation.agentId = agentId;
|
||||
loadConversation.requestedConversationId = conversationId;
|
||||
|
|
|
|||
Loading…
Reference in a new issue