Reimport archived sessions into the current workspace (#2123)

* fix(sessions): reimport archived sessions in their workspace

Archived agent records were treated as active imports, hiding their provider sessions permanently. Workspace-originated imports also discarded their workspace identity and created a duplicate workspace.

* fix(sessions): validate archived session restores

Restore archived imports under their existing Paseo agent identity, reject stale workspace ownership, and gate workspace targeting when the host cannot honor it.

* fix(sessions): roll back failed archived imports

If provider resume or history hydration fails, close any partial runtime, re-archive the provider session, and restore the original stored agent record.

* fix(sessions): validate restored import ownership

* fix(sessions): serialize concurrent restores

* fix(sessions): harden archived import recovery

* fix(sessions): validate archived import placement

* refactor(sessions): centralize provider session imports

Keep workspace placement rollback and stored-agent activation behind their existing owners so Session remains the wire boundary.
This commit is contained in:
Mohamed Boudra
2026-07-17 02:25:50 +08:00
committed by GitHub
parent 7d80fdfd12
commit a1cd50c2ae
18 changed files with 1013 additions and 154 deletions
@@ -9,6 +9,7 @@ import {
getPromptPreview,
getSessionTitle,
resolveProvidersToFetch,
requiresImportSessionsHostUpgrade,
type SessionsQueryResult,
sumFilteredAlreadyImportedCount,
} from "@/components/import-session-sheet-view-model";
@@ -70,6 +71,35 @@ describe("resolveProvidersToFetch", () => {
});
});
describe("requiresImportSessionsHostUpgrade", () => {
it("allows home imports on hosts without workspace targeting", () => {
expect(
requiresImportSessionsHostUpgrade({
supportsSnapshot: true,
workspaceId: null,
supportsWorkspaceTarget: false,
}),
).toBe(false);
});
it("requires host support for imports opened from a workspace", () => {
expect(
requiresImportSessionsHostUpgrade({
supportsSnapshot: true,
workspaceId: "ws-current",
supportsWorkspaceTarget: false,
}),
).toBe(true);
expect(
requiresImportSessionsHostUpgrade({
supportsSnapshot: true,
workspaceId: "ws-current",
supportsWorkspaceTarget: true,
}),
).toBe(false);
});
});
describe("buildProviderLabelMap", () => {
it("returns an empty map when snapshot entries are missing", () => {
expect(buildProviderLabelMap(undefined).size).toBe(0);
@@ -5,6 +5,14 @@ import { i18n } from "@/i18n/i18next";
export const PER_PROVIDER_LIMIT = 15;
export const ALL_FILTER_VALUE = "__all__";
export function requiresImportSessionsHostUpgrade(input: {
supportsSnapshot: boolean;
workspaceId?: string | null;
supportsWorkspaceTarget: boolean;
}): boolean {
return !input.supportsSnapshot || (Boolean(input.workspaceId) && !input.supportsWorkspaceTarget);
}
export interface SessionsQueryResult {
data:
| {
@@ -15,6 +15,7 @@ import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/com
import { getProviderIcon } from "@/components/provider-icons";
import { formatTimeAgo } from "@/utils/time";
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
import { useHostFeature } from "@/runtime/host-features";
import { i18n } from "@/i18n/i18next";
import {
aggregateSessionEntries,
@@ -26,6 +27,7 @@ import {
getSessionTitle,
PER_PROVIDER_LIMIT,
resolveProvidersToFetch,
requiresImportSessionsHostUpgrade,
sumFilteredAlreadyImportedCount,
} from "@/components/import-session-sheet-view-model";
@@ -44,6 +46,7 @@ interface ImportSessionSheetProps {
client: RecentProviderSessionsClient | null;
serverId: string | null;
cwd?: string | null;
workspaceId?: string | null;
onClose: () => void;
onImportedAgent?: (agentId: string) => void;
onImported?: (agent: ImportedAgent) => void;
@@ -260,6 +263,7 @@ export function ImportSessionSheet({
client,
serverId,
cwd,
workspaceId,
onClose,
onImportedAgent,
onImported,
@@ -272,10 +276,16 @@ export function ImportSessionSheet({
cwd,
enabled: visible,
});
const supportsWorkspaceTarget = useHostFeature(serverId, "importSessionWorkspaceTarget");
const requiresHostUpgrade = requiresImportSessionsHostUpgrade({
supportsSnapshot,
workspaceId,
supportsWorkspaceTarget,
});
const providersToFetch = useMemo(
() => resolveProvidersToFetch(supportsSnapshot, snapshotEntries),
[supportsSnapshot, snapshotEntries],
() => (requiresHostUpgrade ? null : resolveProvidersToFetch(supportsSnapshot, snapshotEntries)),
[requiresHostUpgrade, supportsSnapshot, snapshotEntries],
);
const providerLabelById = useMemo(
@@ -408,6 +418,7 @@ export function ImportSessionSheet({
providerId: entry.providerId,
providerHandleId: entry.providerHandleId,
cwd: entry.cwd,
...(workspaceId ? { workspaceId } : {}),
});
return agent;
},
@@ -450,7 +461,7 @@ export function ImportSessionSheet({
[isRefreshing, handleRefresh, t],
);
const isSnapshotUnsupported = !supportsSnapshot;
const isSnapshotUnsupported = requiresHostUpgrade;
const isWaitingForSnapshot = supportsSnapshot && snapshotEntries === undefined;
const hasNoImportableProviders = providersToFetch !== null && providersToFetch.length === 0;
const isQueryingProviders = queries.length > 0;
@@ -3709,6 +3709,7 @@ function WorkspaceScreenContent({
client={client}
serverId={normalizedServerId}
cwd={workspaceDirectory}
workspaceId={normalizedWorkspaceId}
onClose={closeImportSheet}
onImportedAgent={handleImportedAgent}
/>