|
|
|
@@ -8,7 +8,9 @@ import { connectWorkbenchEvents, type WorkbenchEventStream, type WorkbenchRealti
|
|
|
|
|
import { mergeRunnerTrace, snapshotToRunnerTrace, type TraceSnapshot } from "@/composables/useTraceSubscription";
|
|
|
|
|
import type { AgentChatResponse, AgentChatResultResponse, AgentRunProvenance, ApiResult, ChatMessage, ConversationRecord, LiveSurface, ProviderProfile, TraceEvent, WorkspaceRecord } from "@/types";
|
|
|
|
|
import { DEFAULT_WORKBENCH_PROJECT_ID, firstNonEmptyString, nextProtocolId, normalizeWorkbenchConversationId, rememberWorkbenchProjectId, resolveInitialWorkbenchProjectId, workspaceProjectId } from "@/utils";
|
|
|
|
|
import { RECENT_DRAFTS_STORAGE_KEY, activeTraceIdFromWorkspace, defaultProviderProfileOptions, mergeConversationIntoList, normalizeChatMessageStatus, normalizeRecentDrafts, normalizeWorkbenchMessageTitle, providerProfileOptionsFromPayload, recordRecentDraft, resolveCancelableAgentMessage, resolveComposerState, selectedConversationIdFromWorkspace, shouldApplyWorkspaceSnapshot, shouldShowSessionListLoading, sortSessionTabs, stableConversationList, workspaceWithClearedActiveTrace, type DraftEntry, type ProviderProfileOption, type SessionStatusAuthority, type TurnStatusAuthority } from "./workbench-session";
|
|
|
|
|
import { RECENT_DRAFTS_STORAGE_KEY, defaultProviderProfileOptions, mergeConversationIntoList, normalizeChatMessageStatus, normalizeRecentDrafts, normalizeWorkbenchMessageTitle, providerProfileOptionsFromPayload, recordRecentDraft, resolveCancelableAgentMessage, resolveComposerState, selectedConversationIdFromWorkspace, shouldApplyWorkspaceSnapshot, shouldShowSessionListLoading, sortSessionTabs, stableConversationList, workspaceWithClearedActiveTrace, type DraftEntry, type ProviderProfileOption, type SessionStatusAuthority, type TurnStatusAuthority } from "./workbench-session";
|
|
|
|
|
import { initialWorkbenchConversationIdFromLocation, nextExplicitWorkbenchConversationId, resolveWorkbenchActiveConversationId } from "./workbench-projection";
|
|
|
|
|
import { createWorkbenchServerState, reduceWorkbenchServerState, selectSessionStatusAuthority, selectTraceAuthorityById, selectTurnStatusAuthority, type WorkbenchServerAction } from "./workbench-server-state";
|
|
|
|
|
|
|
|
|
|
const DEFAULT_CODE_AGENT_TIMEOUT_MS = 1_800_000;
|
|
|
|
|
const DEFAULT_GATEWAY_TIMEOUT_MS = 120_000;
|
|
|
|
@@ -40,10 +42,12 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const error = ref<string | null>(null);
|
|
|
|
|
const activityRef = ref({ lastActivityAt: Date.now(), lastActivityIso: new Date().toISOString(), waitingFor: "idle", lastEventLabel: null as string | null });
|
|
|
|
|
const currentRequest = ref<{ traceId: string; conversationId: string | null; sessionId: string | null; threadId: string | null; status?: string | null } | null>(null);
|
|
|
|
|
const explicitConversationId = ref<string | null>(initialWorkbenchConversationIdFromLocation());
|
|
|
|
|
const workspaceSelectionEpoch = ref(0);
|
|
|
|
|
const sessionStatusAuthority = ref<Record<string, SessionStatusAuthority>>({});
|
|
|
|
|
const turnStatusAuthority = ref<Record<string, TurnStatusAuthority>>({});
|
|
|
|
|
const traceAuthorityById = ref<Record<string, NonNullable<ChatMessage["runnerTrace"]>>>({});
|
|
|
|
|
const serverState = ref(createWorkbenchServerState());
|
|
|
|
|
const sessionStatusAuthority = computed(() => selectSessionStatusAuthority(serverState.value));
|
|
|
|
|
const turnStatusAuthority = computed(() => selectTurnStatusAuthority(serverState.value));
|
|
|
|
|
const traceAuthorityById = computed(() => selectTraceAuthorityById(serverState.value));
|
|
|
|
|
const traceHydrationInFlight = new Set<string>();
|
|
|
|
|
const terminalRealtimeRefreshInFlight = new Set<string>();
|
|
|
|
|
let realtimeStream: WorkbenchEventStream | null = null;
|
|
|
|
@@ -51,10 +55,10 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
let realtimeGapTimer: number | null = null;
|
|
|
|
|
|
|
|
|
|
const visibleConversations = computed(() => conversations.value);
|
|
|
|
|
const activeConversationId = computed(() => firstNonEmptyString(switchingConversationId.value, workspace.value?.selectedConversationId, workspace.value?.workspace?.selectedConversationId, messages.value.find((message) => message.conversationId)?.conversationId));
|
|
|
|
|
const selectedSessionId = computed(() => firstNonEmptyString(workspace.value?.selectedAgentSessionId, workspace.value?.workspace?.selectedAgentSessionId, activeConversation.value?.sessionId));
|
|
|
|
|
const selectedThreadId = computed(() => firstNonEmptyString(workspace.value?.workspace?.threadId, activeConversation.value?.threadId));
|
|
|
|
|
const activeConversationId = computed(() => resolveWorkbenchActiveConversationId({ switchingConversationId: switchingConversationId.value, explicitConversationId: explicitConversationId.value }));
|
|
|
|
|
const activeConversation = computed(() => visibleConversations.value.find((item) => item.conversationId === activeConversationId.value) ?? null);
|
|
|
|
|
const selectedSessionId = computed(() => firstNonEmptyString(activeConversation.value?.sessionId));
|
|
|
|
|
const selectedThreadId = computed(() => firstNonEmptyString(activeConversation.value?.threadId));
|
|
|
|
|
const sessionTabs = computed(() => sortSessionTabs(visibleConversations.value, activeConversationId.value, sessionStatusAuthority.value));
|
|
|
|
|
const sessionListLoading = computed(() => shouldShowSessionListLoading({ loading: loading.value, conversationsReady: conversationsReady.value }));
|
|
|
|
|
const conversationDetailLoading = computed(() => Boolean(conversationDetailLoadingId.value || switchingConversationId.value || (loading.value && messages.value.length === 0)));
|
|
|
|
@@ -67,9 +71,10 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrate(options: HydrateOptions = {}): Promise<void> {
|
|
|
|
|
const routeConversationId = normalizeWorkbenchConversationId(options.conversationId);
|
|
|
|
|
if (routeConversationId) setActiveConversationSelection(routeConversationId);
|
|
|
|
|
const requestEpoch = workspaceSelectionEpoch.value;
|
|
|
|
|
const previousConversationId = activeConversationId.value;
|
|
|
|
|
const routeConversationId = normalizeWorkbenchConversationId(options.conversationId);
|
|
|
|
|
if (routeConversationId) conversationDetailLoadingId.value = routeConversationId;
|
|
|
|
|
conversationsReady.value = conversations.value.length > 0;
|
|
|
|
|
loading.value = true;
|
|
|
|
@@ -83,13 +88,17 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
const nextWorkspace = workspaceResult.data?.workspace ?? null;
|
|
|
|
|
const nextProjectId = workspaceProjectId(nextWorkspace, projectId.value);
|
|
|
|
|
rememberWorkspaceSnapshot(nextProjectId, nextWorkspace);
|
|
|
|
|
projectId.value = nextProjectId;
|
|
|
|
|
const selectedConversationId = routeConversationId ?? selectedConversationIdFromWorkspace(nextWorkspace);
|
|
|
|
|
const selectedConversationId = routeConversationId ?? activeConversationId.value ?? selectedConversationIdFromWorkspace(nextWorkspace);
|
|
|
|
|
bootstrapActiveConversationSelection(selectedConversationId);
|
|
|
|
|
const conversationsPromise = api.workbench.conversations(nextProjectId, { includeConversationId: selectedConversationId });
|
|
|
|
|
if (selectedConversationId) conversationDetailLoadingId.value = selectedConversationId;
|
|
|
|
|
const selectedConversationResult = selectedConversationId ? await api.workbench.conversation(selectedConversationId, { projectId: nextProjectId }) : null;
|
|
|
|
|
clearConversationDetailLoading(selectedConversationId);
|
|
|
|
|
const selectedConversation = selectedConversationResult?.ok ? selectedConversationResult.data?.conversation ?? null : null;
|
|
|
|
|
rememberConversationDetail(selectedConversation);
|
|
|
|
|
if (selectedConversation) conversations.value = mergeConversationIntoList(conversations.value, selectedConversation);
|
|
|
|
|
if (selectedConversationId && selectedConversationResult && !selectedConversationResult.ok) error.value = selectedConversationResult.error ?? "conversation unavailable";
|
|
|
|
|
const workspaceSnapshot = selectedConversation
|
|
|
|
|
? workspaceWithSelectedConversation(nextWorkspace, selectedConversation, conversationProjectId(selectedConversation, nextProjectId))
|
|
|
|
@@ -98,6 +107,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
: nextWorkspace;
|
|
|
|
|
if (shouldApplyWorkspaceSnapshot({ requestEpoch, currentEpoch: workspaceSelectionEpoch.value, currentConversationId: activeConversationId.value, workspace: workspaceSnapshot })) {
|
|
|
|
|
workspace.value = workspaceSnapshot;
|
|
|
|
|
rememberWorkspaceSnapshot(nextProjectId, workspaceSnapshot);
|
|
|
|
|
providerProfile.value = firstNonEmptyString(workspace.value?.providerProfile, workspace.value?.workspace?.providerProfile, providerProfile.value) ?? providerProfile.value;
|
|
|
|
|
rememberWorkbenchProjectId(workspaceProjectId(workspace.value, nextProjectId));
|
|
|
|
|
messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(selectedConversation, selectedConversationId, previousConversationId, messages.value), messages.value);
|
|
|
|
@@ -112,12 +122,14 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
if (conversationsResult.ok) {
|
|
|
|
|
nextConversations = stableConversationList(conversations.value, conversationsResult.data?.conversations, selectedConversationId, selectedConversation);
|
|
|
|
|
conversations.value = nextConversations;
|
|
|
|
|
rememberConversationList(nextConversations);
|
|
|
|
|
conversationsReady.value = true;
|
|
|
|
|
void refreshSessionStatusAuthority(nextConversations);
|
|
|
|
|
} else {
|
|
|
|
|
if (selectedConversation) {
|
|
|
|
|
nextConversations = mergeConversationIntoList(nextConversations, selectedConversation);
|
|
|
|
|
conversations.value = nextConversations;
|
|
|
|
|
rememberConversationDetail(selectedConversation);
|
|
|
|
|
}
|
|
|
|
|
conversationsReady.value = conversations.value.length > 0;
|
|
|
|
|
error.value = conversations.value.length > 0 ? null : conversationsResult.error ?? "session list unavailable";
|
|
|
|
@@ -147,6 +159,9 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
if (response.ok && isCurrentWorkspaceSelection(requestEpoch)) {
|
|
|
|
|
workspace.value = response.data?.workspace ?? current;
|
|
|
|
|
rememberWorkspaceSnapshot(activeProjectId.value, workspace.value);
|
|
|
|
|
setActiveConversationSelection(selectedConversationIdFromWorkspace(workspace.value));
|
|
|
|
|
rememberConversationDetail(workspace.value?.selectedConversation);
|
|
|
|
|
messages.value = restoreMessagesTraceAuthority(messagesFromWorkspace(workspace.value), messages.value);
|
|
|
|
|
currentRequest.value = null;
|
|
|
|
|
void hydrateTurnStatusAuthority(messages.value);
|
|
|
|
@@ -163,10 +178,13 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const requestEpoch = beginWorkspaceSelection();
|
|
|
|
|
const tabProjectId = conversation.projectId ?? activeProjectId.value;
|
|
|
|
|
switchingConversationId.value = conversation.conversationId;
|
|
|
|
|
setActiveConversationSelection(conversation.conversationId);
|
|
|
|
|
conversationDetailLoadingId.value = conversation.conversationId;
|
|
|
|
|
conversations.value = mergeConversationIntoList(conversations.value, conversation);
|
|
|
|
|
rememberConversationDetail(conversation);
|
|
|
|
|
conversationsReady.value = true;
|
|
|
|
|
workspace.value = optimisticWorkspaceSelection(current, conversation, tabProjectId);
|
|
|
|
|
rememberWorkspaceSnapshot(tabProjectId, workspace.value);
|
|
|
|
|
void refreshSessionStatusById(sessionIdFromConversation(conversation));
|
|
|
|
|
loading.value = true;
|
|
|
|
|
const persistPromise = api.workbench.selectConversation(current.workspaceId, { projectId: tabProjectId, conversationId: conversation.conversationId, sessionId: conversation.sessionId, threadId: conversation.threadId, updatedByClient: "cloud-web-vue" });
|
|
|
|
@@ -197,6 +215,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
error.value = "invalid session URL";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
setActiveConversationSelection(normalized);
|
|
|
|
|
const current = await ensureWorkspace();
|
|
|
|
|
if (!current) return false;
|
|
|
|
|
if (activeConversationId.value === normalized && messages.value.length > 0) return true;
|
|
|
|
@@ -233,6 +252,9 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const response = await api.workbench.deleteConversation(conversationId, { projectId: activeProjectId.value, workspaceId: workspace.value?.workspaceId, updatedByClient: "cloud-web-vue" });
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
workspace.value = response.data?.workspace ?? workspace.value;
|
|
|
|
|
rememberWorkspaceSnapshot(activeProjectId.value, workspace.value);
|
|
|
|
|
replaceActiveConversationSelection(selectedConversationIdFromWorkspace(workspace.value));
|
|
|
|
|
rememberConversationDetail(workspace.value?.selectedConversation);
|
|
|
|
|
messages.value = restoreMessagesTraceAuthority(messagesFromWorkspace(workspace.value), messages.value);
|
|
|
|
|
currentRequest.value = null;
|
|
|
|
|
void hydrateTurnStatusAuthority(messages.value);
|
|
|
|
@@ -244,6 +266,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const response = await api.workbench.conversations(activeProjectId.value, { includeConversationId });
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
conversations.value = stableConversationList(conversations.value, response.data?.conversations, includeConversationId, activeConversation.value);
|
|
|
|
|
rememberConversationList(conversations.value);
|
|
|
|
|
conversationsReady.value = true;
|
|
|
|
|
await refreshSessionStatusAuthority(conversations.value);
|
|
|
|
|
return;
|
|
|
|
@@ -252,6 +275,34 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
if (conversations.value.length === 0) error.value = response.error ?? "session list unavailable";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reduceServerState(action: WorkbenchServerAction): void {
|
|
|
|
|
serverState.value = reduceWorkbenchServerState(serverState.value, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rememberWorkspaceSnapshot(snapshotProjectId: string, snapshot: WorkspaceRecord | null): void {
|
|
|
|
|
reduceServerState({ type: "workspace.snapshot", projectId: snapshotProjectId, workspace: snapshot });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rememberConversationDetail(conversation: ConversationRecord | null | undefined): void {
|
|
|
|
|
reduceServerState({ type: "conversation.detail", conversation });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rememberConversationList(source: ConversationRecord[]): void {
|
|
|
|
|
reduceServerState({ type: "conversation.list", conversations: source });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setActiveConversationSelection(conversationId: string | null | undefined): void {
|
|
|
|
|
explicitConversationId.value = nextExplicitWorkbenchConversationId(explicitConversationId.value, conversationId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function replaceActiveConversationSelection(conversationId: string | null | undefined): void {
|
|
|
|
|
explicitConversationId.value = normalizeWorkbenchConversationId(conversationId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function bootstrapActiveConversationSelection(conversationId: string | null | undefined): void {
|
|
|
|
|
if (!explicitConversationId.value) setActiveConversationSelection(conversationId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshSessionStatusAuthority(source: ConversationRecord[] = conversations.value): Promise<void> {
|
|
|
|
|
await Promise.all(uniqueSessionIds(source).map((sessionId) => refreshSessionStatusById(sessionId)));
|
|
|
|
|
}
|
|
|
|
@@ -259,9 +310,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
async function refreshSelectedSessionStatus(fallbackConversation?: ConversationRecord | null): Promise<void> {
|
|
|
|
|
await refreshSessionStatusById(firstNonEmptyString(
|
|
|
|
|
fallbackConversation ? sessionIdFromConversation(fallbackConversation) : null,
|
|
|
|
|
selectedSessionId.value,
|
|
|
|
|
workspace.value?.selectedAgentSessionId,
|
|
|
|
|
workspace.value?.workspace?.selectedAgentSessionId
|
|
|
|
|
selectedSessionId.value
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -271,23 +320,21 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const response = await api.agent.getAgentSession(id);
|
|
|
|
|
const session = response.data?.session && typeof response.data.session === "object" ? response.data.session : null;
|
|
|
|
|
if (!response.ok || !session) {
|
|
|
|
|
const next = { ...sessionStatusAuthority.value };
|
|
|
|
|
delete next[id];
|
|
|
|
|
sessionStatusAuthority.value = next;
|
|
|
|
|
reduceServerState({ type: "session.forget", sessionId: id });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const returnedSessionId = firstNonEmptyString(session.sessionId, id);
|
|
|
|
|
if (returnedSessionId !== id) return;
|
|
|
|
|
sessionStatusAuthority.value = {
|
|
|
|
|
...sessionStatusAuthority.value,
|
|
|
|
|
[id]: {
|
|
|
|
|
reduceServerState({
|
|
|
|
|
type: "session.status",
|
|
|
|
|
session: {
|
|
|
|
|
sessionId: id,
|
|
|
|
|
status: firstNonEmptyString(session.status) ?? null,
|
|
|
|
|
updatedAt: firstNonEmptyString(session.updatedAt) ?? null,
|
|
|
|
|
lastTraceId: firstNonEmptyString(session.lastTraceId) ?? null,
|
|
|
|
|
loadedAt: new Date().toISOString()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrateTurnStatusAuthority(source: ChatMessage[] = messages.value): Promise<void> {
|
|
|
|
@@ -302,9 +349,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
applyTurnStatusSnapshot(id, response.data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const next = { ...turnStatusAuthority.value };
|
|
|
|
|
delete next[id];
|
|
|
|
|
turnStatusAuthority.value = next;
|
|
|
|
|
reduceServerState({ type: "turn.forget", traceId: id });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyTurnStatusSnapshot(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
|
|
|
|
@@ -318,9 +363,9 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const status = normalizedStatusText(result.status) ?? null;
|
|
|
|
|
const running = (result as AgentChatResultResponse).running === true || isTraceActiveStatus(status);
|
|
|
|
|
const terminal = (result as AgentChatResultResponse).terminal === true || isTerminalMessageStatus(status);
|
|
|
|
|
turnStatusAuthority.value = {
|
|
|
|
|
...turnStatusAuthority.value,
|
|
|
|
|
[id]: {
|
|
|
|
|
reduceServerState({
|
|
|
|
|
type: "turn.status",
|
|
|
|
|
turn: {
|
|
|
|
|
traceId: id,
|
|
|
|
|
status,
|
|
|
|
|
running,
|
|
|
|
@@ -331,7 +376,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
updatedAt: firstNonEmptyString((result as AgentChatResultResponse).updatedAt) ?? null,
|
|
|
|
|
loadedAt: new Date().toISOString()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function syncTurnStatusToMessage(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
|
|
|
|
@@ -364,6 +409,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const traceId = steerMode && composer.value.targetTraceId ? composer.value.targetTraceId : nextProtocolId("trc");
|
|
|
|
|
const steerTraceId = steerMode ? nextProtocolId("trc_steer") : null;
|
|
|
|
|
const conversationId = activeConversationId.value ?? nextProtocolId("cnv");
|
|
|
|
|
setActiveConversationSelection(conversationId);
|
|
|
|
|
const sessionId = composer.value.sessionId;
|
|
|
|
|
const threadId = composer.value.threadId;
|
|
|
|
|
const user = makeMessage("user", value, "sent", { traceId: steerTraceId ?? traceId, conversationId, sessionId, threadId, title: steerMode ? "用户引导" : "用户" });
|
|
|
|
@@ -495,7 +541,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const existing = traceAuthorityById.value[traceId] ?? null;
|
|
|
|
|
if (trace.eventSource !== "trace-api" && !traceHasEvents(trace) && !existing) return;
|
|
|
|
|
const nextTrace = mergeRunnerTrace(existing, trace as NonNullable<ChatMessage["runnerTrace"]>);
|
|
|
|
|
traceAuthorityById.value = { ...traceAuthorityById.value, [traceId]: nextTrace };
|
|
|
|
|
reduceServerState({ type: "trace.snapshot", traceId, trace: nextTrace });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyTraceHydrationResult(traceId: string, result: AgentChatResultResponse): void {
|
|
|
|
@@ -568,7 +614,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
function clearConversation(): void {
|
|
|
|
|
messages.value = [];
|
|
|
|
|
currentRequest.value = null;
|
|
|
|
|
const activeTraceId = activeTraceIdFromWorkspace(workspace.value);
|
|
|
|
|
const activeTraceId = activeTraceIdFromMessages(messages.value, turnStatusAuthority.value);
|
|
|
|
|
if (activeTraceId) void clearActiveTrace(activeTraceId, "clear-conversation");
|
|
|
|
|
restartRealtime("clear-conversation");
|
|
|
|
|
}
|
|
|
|
@@ -625,7 +671,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function realtimeTraceId(): string | null {
|
|
|
|
|
return firstNonEmptyString(currentRequest.value?.traceId, activeTraceIdFromMessages(messages.value, turnStatusAuthority.value), activeTraceIdFromWorkspace(workspace.value));
|
|
|
|
|
return firstNonEmptyString(currentRequest.value?.traceId, activeTraceIdFromMessages(messages.value, turnStatusAuthority.value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyRealtimeEvent(event: WorkbenchRealtimeEvent, eventName: string): void {
|
|
|
|
@@ -656,6 +702,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
if (!shouldApplyWorkspaceSnapshot({ requestEpoch: workspaceSelectionEpoch.value, currentEpoch: workspaceSelectionEpoch.value, currentConversationId: activeConversationId.value, workspace: nextWorkspace })) return;
|
|
|
|
|
const previousTraceId = realtimeTraceId();
|
|
|
|
|
workspace.value = nextWorkspace;
|
|
|
|
|
rememberWorkspaceSnapshot(workspaceProjectId(nextWorkspace, activeProjectId.value), nextWorkspace);
|
|
|
|
|
providerProfile.value = firstNonEmptyString(workspace.value?.providerProfile, workspace.value?.workspace?.providerProfile, providerProfile.value) ?? providerProfile.value;
|
|
|
|
|
rememberWorkbenchProjectId(workspaceProjectId(workspace.value, activeProjectId.value));
|
|
|
|
|
const nextTraceId = realtimeTraceId();
|
|
|
|
@@ -852,7 +899,10 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
workspace: current.workspace,
|
|
|
|
|
updatedByClient: "cloud-web-vue-active-trace-repair"
|
|
|
|
|
});
|
|
|
|
|
if (response.ok && response.data?.workspace && shouldApplyWorkspaceSnapshot({ requestEpoch, currentEpoch: workspaceSelectionEpoch.value, currentConversationId: activeConversationId.value, workspace: response.data.workspace })) workspace.value = response.data.workspace;
|
|
|
|
|
if (response.ok && response.data?.workspace && shouldApplyWorkspaceSnapshot({ requestEpoch, currentEpoch: workspaceSelectionEpoch.value, currentConversationId: activeConversationId.value, workspace: response.data.workspace })) {
|
|
|
|
|
workspace.value = response.data.workspace;
|
|
|
|
|
rememberWorkspaceSnapshot(workspaceProjectId(workspace.value, activeProjectId.value), workspace.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function beginWorkspaceSelection(): number {
|
|
|
|
@@ -875,7 +925,10 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applySelectedConversationDetail(conversation: ConversationRecord, baseWorkspace: WorkspaceRecord | null, selectedProjectId: string, previousConversationId: string | null, previousMessages: ChatMessage[]): void {
|
|
|
|
|
setActiveConversationSelection(conversation.conversationId);
|
|
|
|
|
workspace.value = workspaceWithSelectedConversation(baseWorkspace, conversation, selectedProjectId);
|
|
|
|
|
rememberWorkspaceSnapshot(selectedProjectId, workspace.value);
|
|
|
|
|
rememberConversationDetail(conversation);
|
|
|
|
|
messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(conversation, conversation.conversationId, previousConversationId, previousMessages), previousMessages);
|
|
|
|
|
conversations.value = mergeConversationIntoList(conversations.value, conversation);
|
|
|
|
|
conversationsReady.value = true;
|
|
|
|
@@ -904,6 +957,8 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
if (!response.ok || !response.data?.workspace || activeConversationId.value !== conversationId) return;
|
|
|
|
|
workspace.value = workspaceWithSelectedConversation(response.data.workspace, conversation, selectedProjectId);
|
|
|
|
|
rememberWorkspaceSnapshot(selectedProjectId, workspace.value);
|
|
|
|
|
rememberConversationDetail(conversation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function retrySelectConversationWithFreshWorkspace(conversation: ConversationRecord, tabProjectId: string, requestEpoch: number): Promise<boolean> {
|
|
|
|
@@ -915,7 +970,10 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
const freshWorkspace = fresh.data?.workspace;
|
|
|
|
|
if (!fresh.ok || !freshWorkspace?.workspaceId) return false;
|
|
|
|
|
setActiveConversationSelection(conversation.conversationId);
|
|
|
|
|
workspace.value = optimisticWorkspaceSelection(freshWorkspace, conversation, tabProjectId);
|
|
|
|
|
rememberWorkspaceSnapshot(tabProjectId, workspace.value);
|
|
|
|
|
rememberConversationDetail(conversation);
|
|
|
|
|
const [retried, detailResponse] = await Promise.all([
|
|
|
|
|
api.workbench.selectConversation(freshWorkspace.workspaceId, { projectId: tabProjectId, conversationId: conversation.conversationId, sessionId: conversation.sessionId, threadId: conversation.threadId, updatedByClient: "cloud-web-vue-retry" }),
|
|
|
|
|
api.workbench.conversation(conversation.conversationId, { projectId: tabProjectId })
|
|
|
|
@@ -932,8 +990,13 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
const selectedConversation = detailResponse.ok ? detailResponse.data?.conversation ?? null : null;
|
|
|
|
|
if (!selectedConversation) error.value = detailResponse.error ?? "conversation unavailable";
|
|
|
|
|
workspace.value = workspaceWithSelectedConversation(retried.data?.workspace ?? workspace.value, selectedConversation ?? conversation, conversationProjectId(selectedConversation ?? conversation, tabProjectId));
|
|
|
|
|
rememberWorkspaceSnapshot(tabProjectId, workspace.value);
|
|
|
|
|
rememberConversationDetail(selectedConversation ?? conversation);
|
|
|
|
|
messages.value = messagesFromSelectedConversation(selectedConversation, conversation.conversationId, null, []);
|
|
|
|
|
if (selectedConversation) conversations.value = mergeConversationIntoList(conversations.value, selectedConversation);
|
|
|
|
|
if (selectedConversation) {
|
|
|
|
|
conversations.value = mergeConversationIntoList(conversations.value, selectedConversation);
|
|
|
|
|
rememberConversationDetail(selectedConversation);
|
|
|
|
|
}
|
|
|
|
|
void hydrateTurnStatusAuthority(messages.value);
|
|
|
|
|
void hydrateTerminalMessageDiagnostics();
|
|
|
|
|
void hydrateTraceEvents(messages.value);
|
|
|
|
@@ -950,7 +1013,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
|
|
|
|
|
function reattachRestoredActiveTrace(): void {
|
|
|
|
|
void hydrateTraceEvents(messages.value);
|
|
|
|
|
const traceId = firstNonEmptyString(activeTraceIdFromMessages(messages.value, turnStatusAuthority.value), activeTraceIdFromWorkspace(workspace.value));
|
|
|
|
|
const traceId = activeTraceIdFromMessages(messages.value, turnStatusAuthority.value);
|
|
|
|
|
if (traceId) void validateAndReattachTrace(traceId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|