Merge pull request #1330 from pikasTech/fix/1329-refresh-restore
fix(web): restore running Code Agent session after refresh
This commit is contained in:
@@ -2742,25 +2742,37 @@ function resolvedConversationStatus(status, snapshot, messages = []) {
|
||||
const snapshotStatus = textOr(snapshot.sessionStatus ?? snapshot.status, "").toLowerCase();
|
||||
const latestAgent = latestAgentConversationMessage(messages);
|
||||
const latestStatus = textOr(latestAgent?.status, "").toLowerCase();
|
||||
if (["running", "busy", "pending", "active"].includes(stored)) {
|
||||
if (snapshotStatus && !["running", "busy", "pending", "active"].includes(snapshotStatus)) return snapshotStatus === "cancelled" ? "canceled" : snapshotStatus;
|
||||
if (latestStatus && !["running", "busy", "pending", "active"].includes(latestStatus)) return latestStatus === "cancelled" ? "canceled" : latestStatus;
|
||||
if (isActiveConversationStatus(stored)) {
|
||||
if (snapshotStatus && !isActiveConversationStatus(snapshotStatus)) return snapshotStatus === "cancelled" ? "canceled" : snapshotStatus;
|
||||
if (latestStatus && !isActiveConversationStatus(latestStatus)) return latestStatus === "cancelled" ? "canceled" : latestStatus;
|
||||
}
|
||||
if (isActiveConversationStatus(latestStatus) && !isTerminalConversationStatus(stored) && !isTerminalConversationStatus(snapshotStatus)) return latestStatus === "active" ? "running" : latestStatus;
|
||||
return stored || snapshotStatus || latestStatus || "idle";
|
||||
}
|
||||
|
||||
function resolvedConversationLastTraceId(storedTraceId, status, messages = [], snapshot = {}) {
|
||||
const latestTraceId = textOr(latestAgentConversationMessage(messages)?.traceId, "");
|
||||
const latestAgent = latestAgentConversationMessage(messages);
|
||||
const latestTraceId = textOr(latestAgent?.traceId, "");
|
||||
const latestStatus = textOr(latestAgent?.status, "").toLowerCase();
|
||||
const snapshotTraceId = textOr(snapshot.lastTraceId, "");
|
||||
const stored = textOr(storedTraceId, "");
|
||||
const snapshotStatus = textOr(snapshot.sessionStatus ?? snapshot.status, "").toLowerCase();
|
||||
if (stored && snapshotStatus && !["running", "busy", "pending", "active"].includes(snapshotStatus)) return stored;
|
||||
if (!["running", "busy", "pending", "active"].includes(textOr(status, "").toLowerCase())) {
|
||||
if (latestTraceId && (isActiveConversationStatus(latestStatus) || isActiveConversationStatus(status))) return latestTraceId;
|
||||
if (stored && snapshotStatus && !isActiveConversationStatus(snapshotStatus)) return stored;
|
||||
if (!isActiveConversationStatus(status)) {
|
||||
return latestTraceId || snapshotTraceId || stored;
|
||||
}
|
||||
return stored || latestTraceId || snapshotTraceId;
|
||||
}
|
||||
|
||||
function isActiveConversationStatus(value) {
|
||||
return ["running", "busy", "pending", "active", "processing", "accepted", "creating"].includes(textOr(value, "").toLowerCase().replace(/_/gu, "-"));
|
||||
}
|
||||
|
||||
function isTerminalConversationStatus(value) {
|
||||
return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed", "interrupted", "expired"].includes(textOr(value, "").toLowerCase().replace(/_/gu, "-"));
|
||||
}
|
||||
|
||||
function latestAgentConversationMessage(messages = []) {
|
||||
if (!Array.isArray(messages)) return null;
|
||||
for (const message of [...messages].reverse()) {
|
||||
|
||||
@@ -169,7 +169,7 @@ test("R2 active in-flight turn updates the selected session tab before list refr
|
||||
assert.equal(tabs[0]?.messageCount, 2);
|
||||
});
|
||||
|
||||
test("R2 session tabs use conversation status before stale snapshot or message status", () => {
|
||||
test("R2 session tabs keep restored running message ahead of stale idle conversation status", () => {
|
||||
const conversation: ConversationRecord = {
|
||||
conversationId: "cnv_stale_snapshot",
|
||||
sessionId: "ses_stale_snapshot",
|
||||
@@ -177,9 +177,10 @@ test("R2 session tabs use conversation status before stale snapshot or message s
|
||||
snapshot: { sessionStatus: "running" },
|
||||
messages: [agentMessage({ conversationId: "cnv_stale_snapshot", status: "running", traceId: "trc_stale" })]
|
||||
};
|
||||
assert.equal(resolveConversationSessionStatus(conversation), "idle");
|
||||
assert.equal(resolveConversationSessionStatus(conversation), "running");
|
||||
const tabs = sortSessionTabs([conversation], "cnv_stale_snapshot");
|
||||
assert.equal(tabs[0]?.running, false);
|
||||
assert.equal(tabs[0]?.running, true);
|
||||
assert.equal(tabs[0]?.lastTraceId, "trc_stale");
|
||||
});
|
||||
|
||||
test("R2 agent message titles drop transient running wording", () => {
|
||||
|
||||
@@ -135,7 +135,7 @@ export function conversationToSessionTab(conversation: ConversationRecord, activ
|
||||
const conversationId = conversation.conversationId;
|
||||
const updatedAt = conversationDisplayUpdatedAt(conversation);
|
||||
const status = resolveConversationSessionStatus(conversation);
|
||||
const trace = firstNonEmptyString(conversation.lastTraceId, latestAgentMessage(conversation.messages)?.traceId, conversation.messages?.at(-1)?.traceId);
|
||||
const trace = firstNonEmptyString(latestAgentMessage(conversation.messages)?.traceId, conversation.messages?.at(-1)?.traceId, conversation.lastTraceId);
|
||||
const userMessage = conversation.messages?.find((message) => message.role === "user");
|
||||
const running = isActiveStatus(status);
|
||||
const preview = firstReadableSentence(
|
||||
@@ -179,6 +179,17 @@ export function resolveConversationSessionStatus(conversation: ConversationRecor
|
||||
latestMessage?.status
|
||||
);
|
||||
if (stored && isActiveStatus(stored) && terminalEvidence) return terminalEvidence;
|
||||
const activeEvidence = firstActiveStatus(
|
||||
latestAgent?.status,
|
||||
latestAgent?.runnerTrace?.status,
|
||||
latestAgent?.runnerTrace?.traceStatus,
|
||||
latestMessage?.status,
|
||||
...extraStatuses,
|
||||
conversation.session?.status,
|
||||
conversation.snapshot?.sessionStatus,
|
||||
conversation.snapshot?.status
|
||||
);
|
||||
if (activeEvidence && !isTerminalStatus(stored)) return activeEvidence;
|
||||
return stored ?? firstNormalizedSessionStatus(
|
||||
...extraStatuses,
|
||||
conversation.session?.status,
|
||||
@@ -285,7 +296,7 @@ function selectedConversationStub(workspace: WorkspaceRecord | null, selectedCon
|
||||
const messages = Array.isArray(workspace.workspace?.messages) ? workspace.workspace.messages : [];
|
||||
const sessionId = firstNonEmptyString(workspace.selectedAgentSessionId, workspace.workspace?.selectedAgentSessionId, messages.find((message) => message.sessionId)?.sessionId) ?? null;
|
||||
const threadId = firstNonEmptyString(workspace.workspace?.threadId, messages.find((message) => message.threadId)?.threadId) ?? null;
|
||||
const lastTraceId = firstNonEmptyString(workspace.workspace?.lastTraceId, workspace.activeTraceId, workspace.workspace?.activeTraceId, [...messages].reverse().find((message) => message.traceId)?.traceId) ?? null;
|
||||
const lastTraceId = firstNonEmptyString([...messages].reverse().find((message) => message.traceId)?.traceId, workspace.workspace?.lastTraceId, workspace.activeTraceId, workspace.workspace?.activeTraceId) ?? null;
|
||||
const lastUserMessageAt = latestUserMessageAtFromMessages(messages);
|
||||
const latestAgent = latestAgentMessage(messages);
|
||||
const stub: ConversationRecord = {
|
||||
@@ -325,7 +336,7 @@ function mergeConversationRecords(existing: ConversationRecord, selected: Conver
|
||||
sessionId: firstNonEmptyString(primary.sessionId, primary.session?.sessionId, supplemental.sessionId, supplemental.session?.sessionId) ?? null,
|
||||
threadId: firstNonEmptyString(primary.threadId, primary.session?.threadId, supplemental.threadId, supplemental.session?.threadId) ?? null,
|
||||
status,
|
||||
lastTraceId: firstNonEmptyString(primary.lastTraceId, supplemental.lastTraceId, latestAgentMessage(messages)?.traceId, messages.at(-1)?.traceId) ?? null,
|
||||
lastTraceId: firstNonEmptyString(latestAgentMessage(messages)?.traceId, messages.at(-1)?.traceId, primary.lastTraceId, supplemental.lastTraceId) ?? null,
|
||||
firstUserMessagePreview: firstNonEmptyString(primary.firstUserMessagePreview, primary.snapshot?.firstUserMessagePreview, supplemental.firstUserMessagePreview, supplemental.snapshot?.firstUserMessagePreview) ?? null,
|
||||
messageCount: messages.length > 0 ? messages.length : primary.messageCount ?? supplemental.messageCount ?? primaryMessages.length,
|
||||
messages: messages.length > 0 ? messages : primary.messages,
|
||||
@@ -378,6 +389,14 @@ function firstNonActiveStatus(...values: unknown[]): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function firstActiveStatus(...values: unknown[]): string | null {
|
||||
for (const value of values) {
|
||||
const status = normalizeSessionStatus(value);
|
||||
if (status && isActiveStatus(status)) return status;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function firstNormalizedSessionStatus(...values: unknown[]): string | null {
|
||||
for (const value of values) {
|
||||
const status = normalizeSessionStatus(value);
|
||||
|
||||
@@ -66,8 +66,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
rememberWorkbenchProjectId(workspaceProjectId(workspace.value, projectId.value));
|
||||
messages.value = messagesFromWorkspaceSelection(workspace.value, nextConversations);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
const activeTraceId = activeTraceIdFromWorkspace(workspace.value);
|
||||
if (activeTraceId) void validateAndReattachTrace(activeTraceId);
|
||||
reattachRestoredActiveTrace();
|
||||
}
|
||||
await refreshProviderOptions();
|
||||
}
|
||||
@@ -117,6 +116,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
workspace.value = response.data?.workspace ?? current;
|
||||
messages.value = messagesFromWorkspaceSelection(workspace.value, [conversation, ...conversations.value]);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
reattachRestoredActiveTrace();
|
||||
currentRequest.value = null;
|
||||
return;
|
||||
}
|
||||
@@ -477,10 +477,16 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
workspace.value = retried.data?.workspace ?? workspace.value;
|
||||
messages.value = messagesFromWorkspaceSelection(workspace.value, [conversation, ...conversations.value]);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
reattachRestoredActiveTrace();
|
||||
currentRequest.value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
function reattachRestoredActiveTrace(): void {
|
||||
const traceId = firstNonEmptyString(activeTraceIdFromMessages(messages.value), activeTraceIdFromWorkspace(workspace.value));
|
||||
if (traceId) void validateAndReattachTrace(traceId);
|
||||
}
|
||||
|
||||
return { projectId, workspace, conversations, messages, providerProfile, providerOptions, recentDrafts, codeAgentTimeoutMs, gatewayShellTimeoutMs, live, loading, sessionListLoading, chatPending, error, activeConversationId, selectedSessionId, selectedThreadId, sessionTabs, composer, hydrate, refreshLive, createSession, selectConversation, selectConversationById, deleteCurrentSession, refreshConversations, submitMessage, cancelAgentMessage, cancelRunningTrace, retryAgentMessage, replayAgentTrace, setProviderProfile, refreshProviderOptions, pickDraft, clearRecentDrafts, clearConversation, recordActivity };
|
||||
});
|
||||
|
||||
@@ -537,13 +543,12 @@ function normalizeChatMessageStatus(value: unknown, runnerTrace: ChatMessage["ru
|
||||
const trace = normalizedStatusText(firstNonEmptyString(runnerTrace?.status, runnerTrace?.traceStatus));
|
||||
const sessionTerminal = terminalChatStatus(session);
|
||||
if (sessionTerminal && !terminalChatStatus(explicit)) return sessionTerminal;
|
||||
if (session && !isTraceActiveStatus(session) && isTraceActiveStatus(explicit)) return "source";
|
||||
const traceTerminal = terminalChatStatus(trace);
|
||||
if (traceTerminal && (!explicit || explicit === "idle" || explicit === "source" || isTraceActiveStatus(explicit))) return traceTerminal;
|
||||
const explicitTerminal = terminalChatStatus(explicit);
|
||||
if (explicitTerminal) return explicitTerminal;
|
||||
if (explicit === "sent") return "sent";
|
||||
if (isTraceActiveStatus(explicit) || (!session || isTraceActiveStatus(session)) && isTraceActiveStatus(trace)) return "running";
|
||||
if (isTraceActiveStatus(explicit) || isTraceActiveStatus(trace)) return "running";
|
||||
if (explicit === "blocked") return "blocked";
|
||||
if (explicit === "timeout") return "timeout";
|
||||
if (explicit === "failed" || explicit === "error") return "failed";
|
||||
@@ -560,6 +565,16 @@ function terminalChatStatus(value: string | null): ChatMessage["status"] | null
|
||||
return null;
|
||||
}
|
||||
|
||||
function activeTraceIdFromMessages(messages: ChatMessage[]): string | null {
|
||||
for (const message of [...messages].reverse()) {
|
||||
if (message.role !== "agent") continue;
|
||||
if (!isTraceActiveStatus(message.status) && !isTraceActiveStatus(message.runnerTrace?.status) && !isTraceActiveStatus(message.runnerTrace?.traceStatus)) continue;
|
||||
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
|
||||
if (traceId) return traceId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizedStatusText(value: unknown): string | null {
|
||||
const text = firstNonEmptyString(value);
|
||||
return text ? text.trim().toLowerCase().replace(/_/gu, "-") : null;
|
||||
|
||||
Reference in New Issue
Block a user