Merge branch 'fix/copilot-mode-per-session' of https://github.com/Significant-Gravitas/AutoGPT into preview/all-prs

This commit is contained in:
majdyz
2026-04-13 09:58:49 +00:00
3 changed files with 85 additions and 2 deletions

View File

@@ -220,6 +220,62 @@ describe("useCopilotUIStore", () => {
const parsed = JSON.parse(raw!) as [string, string][];
expect(parsed).toEqual([["session-1", "fast"]]);
});
it("removes a session mode entry and updates localStorage", () => {
useCopilotUIStore.getState().setCopilotMode("fast");
useCopilotUIStore.getState().recordSessionMode("session-1");
useCopilotUIStore.getState().setCopilotMode("extended_thinking");
useCopilotUIStore.getState().recordSessionMode("session-2");
useCopilotUIStore.getState().removeSessionMode("session-1");
expect(
useCopilotUIStore.getState().sessionModes.has("session-1"),
).toBe(false);
expect(
useCopilotUIStore.getState().sessionModes.get("session-2"),
).toBe("extended_thinking");
// localStorage should only have session-2
const raw = window.localStorage.getItem("copilot-session-modes");
const parsed = JSON.parse(raw!) as [string, string][];
expect(parsed).toEqual([["session-2", "extended_thinking"]]);
});
it("is a no-op when removing a session that was never recorded", () => {
useCopilotUIStore.getState().setCopilotMode("fast");
useCopilotUIStore.getState().recordSessionMode("session-1");
const before = useCopilotUIStore.getState().sessionModes;
useCopilotUIStore.getState().removeSessionMode("unknown-session");
// State reference should not change (no re-render)
expect(useCopilotUIStore.getState().sessionModes).toBe(before);
});
it("ignores invalid mode strings from corrupt localStorage", () => {
window.localStorage.setItem(
"copilot-session-modes",
JSON.stringify([
["session-valid", "fast"],
["session-bad", "invalid_mode"],
["not-a-pair"],
"garbage",
]),
);
// Re-initialise by reading state directly via the getter used at init time
// (simulate a fresh page load by clearing and re-setting the store)
useCopilotUIStore.getState().clearCopilotLocalData();
window.localStorage.setItem(
"copilot-session-modes",
JSON.stringify([
["session-valid", "fast"],
["session-bad", "invalid_mode"],
]),
);
// Force store re-read by calling the internal persistence path
useCopilotUIStore.getState().recordSessionMode("__probe__");
// The corrupt entry should never be readable as a valid CopilotMode
const state = useCopilotUIStore.getState();
expect(state.sessionModes.get("session-bad")).toBeUndefined();
});
});
describe("clearCopilotLocalData", () => {

View File

@@ -79,12 +79,24 @@ function schedulePanelWidthPersist(width: number) {
}, 200);
}
function isCopilotMode(value: unknown): value is CopilotMode {
return value === "fast" || value === "extended_thinking";
}
function getPersistedSessionModes(): Map<string, CopilotMode> {
if (!isClient) return new Map();
try {
const raw = storage.get(Key.COPILOT_SESSION_MODES);
if (raw) {
const entries = JSON.parse(raw) as [string, CopilotMode][];
const parsed = JSON.parse(raw) as unknown;
if (!Array.isArray(parsed)) return new Map();
const entries = parsed.filter(
(entry): entry is [string, CopilotMode] =>
Array.isArray(entry) &&
entry.length === 2 &&
typeof entry[0] === "string" &&
isCopilotMode(entry[1]),
);
return new Map(entries);
}
} catch {
@@ -164,6 +176,8 @@ interface CopilotUIState {
recordSessionMode: (sessionId: string) => void;
/** Restore the copilot mode from a previously recorded session. */
restoreSessionMode: (sessionId: string) => void;
/** Remove the recorded mode for a session (call on session delete). */
removeSessionMode: (sessionId: string) => void;
/** Developer dry-run mode: sessions created with dry_run=true. */
isDryRun: boolean;
@@ -333,8 +347,17 @@ export const useCopilotUIStore = create<CopilotUIState>((set) => ({
storage.set(Key.COPILOT_MODE, mode);
return { copilotMode: mode };
}
// Return same state reference to skip unnecessary re-render
return state;
}),
removeSessionMode: (sessionId) =>
set((state) => {
if (!state.sessionModes.has(sessionId)) return state;
const next = new Map(state.sessionModes);
next.delete(sessionId);
persistSessionModes(next);
return { sessionModes: next };
}),
isDryRun: isClient && storage.get(Key.COPILOT_DRY_RUN) === "true",
setIsDryRun: (enabled) => {

View File

@@ -46,6 +46,7 @@ export function useCopilotPage() {
isDryRun,
recordSessionMode,
restoreSessionMode,
removeSessionMode,
} = useCopilotUIStore();
const {
@@ -109,6 +110,9 @@ export function useCopilotPage() {
if (sessionToDelete?.id === sessionId) {
setSessionId(null);
}
if (sessionToDelete?.id) {
removeSessionMode(sessionToDelete.id);
}
setSessionToDelete(null);
},
onError: (error) => {
@@ -169,7 +173,7 @@ export function useCopilotPage() {
} else {
sendMessage({ text: msg });
}
}, [sessionId, pendingMessage, sendMessage]);
}, [sessionId, pendingMessage, sendMessage, recordSessionMode]);
// --- Extract prompt from URL hash on mount (e.g. /copilot#prompt=Hello) ---
useWorkflowImportAutoSubmit({