feat(onboarding): suggest projects from agent history

The setup's Projects step lists the repos the user already ran agents in,
with busy recent ones preselected, so a new user adds their projects in one
click instead of browsing for folders. With rows checked, the footer's main
button adds them and only moves on once every one was added; a checked row
always means "this will be added".

The daemon resolves each session folder to its project root (main checkout
for worktrees, repo root for subfolders) because only it can read the git
state; the app asks once per provider so a slow or broken provider never
holds up the others and can be retried on its own. Hosts without the
projectSuggestions feature show only the Add project button.

Onboarding e2e hosts read an empty Claude history with other providers
off, so suggestions never depend on the machine running the tests.
This commit is contained in:
Mohamed Boudra
2026-10-31 10:37:51 +01:00
parent 76ba3fdbe0
commit 1284ff355f
29 changed files with 1562 additions and 46 deletions
+21
View File
@@ -869,6 +869,14 @@ export type WorkspaceLabelDeleteInspectPayload = Extract<
SessionOutboundMessage,
{ type: "workspace.label.delete.inspect.response" }
>["payload"];
export type ProjectSuggestionsPayload = Extract<
SessionOutboundMessage,
{ type: "project.suggestions.response" }
>["payload"];
export type ProjectSuggestionsOptions = Omit<
Extract<SessionInboundMessage, { type: "project.suggestions.request" }>,
"type" | "requestId"
> & { requestId?: string };
export type ProjectListPayload = Extract<
SessionOutboundMessage,
{ type: "project.list.response" }
@@ -2976,6 +2984,19 @@ export class DaemonClient {
});
}
/** Repositories the user ran agent sessions in recently, grouped by main checkout. */
listProjectSuggestions(options?: ProjectSuggestionsOptions): Promise<ProjectSuggestionsPayload> {
return this.sendNamespacedCorrelatedSessionRequest<"project.suggestions.response">({
requestId: options?.requestId,
message: {
type: "project.suggestions.request",
...(options?.providers ? { providers: options.providers } : {}),
...(options?.since ? { since: options.since } : {}),
...(options?.limit ? { limit: options.limit } : {}),
},
});
}
async openProject(cwd: string, requestId?: string): Promise<OpenProjectPayload> {
return this.sendCorrelatedSessionRequest({
requestId,