feat: import existing CLI sessions from the home screen
Home screen now leads with action tiles (Add a project, Import session, Setup providers, Pair device) instead of a single button, giving new users a clear path in even before they've added a project. Import session is the onboarding hook: the sheet now works without a workspace context and lists recent sessions from every provider's native history (Claude, Codex, OpenCode, Pi, ACP). Picking one imports the agent and lands the user on its workspace. To make that work end-to-end, the daemon upserts the imported agent's cwd as a workspace so the sidebar surfaces it even when the user never explicitly opened the folder. Also adds a Home entry to cmd+K and a refresh button to the import sheet header for explicit re-fetch.
This commit is contained in:
@@ -66,6 +66,20 @@ interface SessionTestAccess {
|
||||
agentManager: {
|
||||
listAgents(): unknown[];
|
||||
listImportablePersistedAgents(options?: unknown): Promise<PersistedAgentDescriptor[]>;
|
||||
findPersistedAgent(
|
||||
provider: string,
|
||||
providerHandleId: string,
|
||||
options?: unknown,
|
||||
): Promise<PersistedAgentDescriptor | null>;
|
||||
resumeAgentFromPersistence(
|
||||
handle: unknown,
|
||||
overrides?: unknown,
|
||||
preferredId?: string,
|
||||
extras?: unknown,
|
||||
): Promise<unknown>;
|
||||
hydrateTimelineFromProvider(agentId: string): Promise<unknown>;
|
||||
getTimeline(agentId: string): readonly unknown[];
|
||||
setTitle(agentId: string, title: string): Promise<unknown>;
|
||||
};
|
||||
workspaceRegistry: {
|
||||
list(...args: unknown[]): Promise<unknown[]>;
|
||||
@@ -2660,6 +2674,105 @@ test("open_project_request registers a workspace before any agent exists", async
|
||||
expect(response?.payload.workspace?.id).toBe(REPO_CWD);
|
||||
});
|
||||
|
||||
test("import_agent_request registers a workspace for a never-seen cwd", async () => {
|
||||
const emitted: SessionOutboundMessage[] = [];
|
||||
const session = createSessionForWorkspaceTests({
|
||||
onMessage: (message) => {
|
||||
if (isSessionOutboundMessage(message)) emitted.push(message);
|
||||
},
|
||||
});
|
||||
const projects = new Map<string, ReturnType<typeof createPersistedProjectRecord>>();
|
||||
const workspaces = new Map<string, ReturnType<typeof createPersistedWorkspaceRecord>>();
|
||||
const importedCwd = path.resolve("/tmp/imported-project");
|
||||
|
||||
session.projectRegistry.get = async (projectId: string) => projects.get(projectId) ?? null;
|
||||
session.projectRegistry.upsert = async (
|
||||
record: ReturnType<typeof createPersistedProjectRecord>,
|
||||
) => {
|
||||
projects.set(record.projectId, record);
|
||||
};
|
||||
session.workspaceRegistry.get = async (workspaceId: string) =>
|
||||
workspaces.get(workspaceId) ?? null;
|
||||
session.workspaceRegistry.upsert = async (
|
||||
record: ReturnType<typeof createPersistedWorkspaceRecord>,
|
||||
) => {
|
||||
workspaces.set(record.workspaceId, record);
|
||||
};
|
||||
session.projectRegistry.list = async () => Array.from(projects.values());
|
||||
session.workspaceRegistry.list = async () => Array.from(workspaces.values());
|
||||
session.buildProjectPlacement = async (cwd: string) => ({
|
||||
projectKey: cwd,
|
||||
projectName: "imported",
|
||||
checkout: {
|
||||
cwd,
|
||||
isGit: false,
|
||||
currentBranch: null,
|
||||
remoteUrl: null,
|
||||
worktreeRoot: null,
|
||||
isPaseoOwnedWorktree: false,
|
||||
mainRepoRoot: null,
|
||||
},
|
||||
});
|
||||
|
||||
const managed = makeManagedAgent({
|
||||
id: "imported-agent",
|
||||
cwd: importedCwd,
|
||||
lifecycle: "idle",
|
||||
updatedAt: "2026-05-21T00:00:00.000Z",
|
||||
});
|
||||
session.agentManager.listAgents = () => [managed];
|
||||
session.agentManager.findPersistedAgent = async () => null;
|
||||
session.agentManager.resumeAgentFromPersistence = async () => managed;
|
||||
session.agentManager.hydrateTimelineFromProvider = async () => undefined;
|
||||
session.agentManager.getTimeline = () => [];
|
||||
session.agentManager.setTitle = async () => undefined;
|
||||
session.agentStorage.list = async () => [];
|
||||
session.agentStorage.get = async () => null;
|
||||
session.forwardAgentUpdate = async () => undefined;
|
||||
|
||||
session.workspaceUpdatesSubscription = {
|
||||
subscriptionId: "sub-import",
|
||||
filter: undefined,
|
||||
isBootstrapping: false,
|
||||
pendingUpdatesByWorkspaceId: new Map(),
|
||||
lastEmittedByWorkspaceId: new Map(),
|
||||
};
|
||||
session.buildWorkspaceDescriptorMap = async () =>
|
||||
new Map([
|
||||
[
|
||||
importedCwd,
|
||||
{
|
||||
id: importedCwd,
|
||||
projectId: importedCwd,
|
||||
projectDisplayName: "imported-project",
|
||||
projectRootPath: importedCwd,
|
||||
projectKind: "non_git",
|
||||
workspaceKind: "directory",
|
||||
name: "imported-project",
|
||||
status: "done",
|
||||
activityAt: null,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
await session.handleMessage({
|
||||
type: "import_agent_request",
|
||||
requestId: "req-import",
|
||||
providerId: "codex",
|
||||
providerHandleId: "session-xyz",
|
||||
cwd: importedCwd,
|
||||
});
|
||||
|
||||
expect(workspaces.get(importedCwd)).toBeTruthy();
|
||||
const workspaceUpdates = filterByType(emitted, "workspace_update");
|
||||
expect(workspaceUpdates.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
workspaceUpdates.some(
|
||||
(update) => update.payload.kind === "upsert" && update.payload.workspace.id === importedCwd,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("open_project_response returns immediately even when the GitHub fetch is slow", async () => {
|
||||
const emitted: SessionOutboundMessage[] = [];
|
||||
const session = createSessionForWorkspaceTests();
|
||||
|
||||
Reference in New Issue
Block a user