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:
@@ -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}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user