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
+34
View File
@@ -1370,6 +1370,15 @@ export const ProjectListRequestMessageSchema = z.object({
sync: DirectorySyncRequestSchema.optional(),
});
export const ProjectSuggestionsRequestSchema = z.object({
type: z.literal("project.suggestions.request"),
requestId: z.string(),
providers: z.array(z.string()).optional(),
// ISO timestamp. Sessions older than this are ignored. Defaults to 30 days ago.
since: z.string().optional(),
limit: z.number().int().positive().max(100).optional(),
});
export const FetchAgentHistoryRequestMessageSchema = z.object({
type: z.literal("fetch_agent_history_request"),
requestId: z.string(),
@@ -3296,6 +3305,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
FetchRecentProviderSessionsRequestMessageSchema,
FetchWorkspacesRequestMessageSchema,
ProjectListRequestMessageSchema,
ProjectSuggestionsRequestSchema,
FetchAgentRequestMessageSchema,
DeleteAgentRequestMessageSchema,
ArchiveAgentRequestMessageSchema,
@@ -3813,6 +3823,8 @@ export const ServerInfoStatusPayloadSchema = z
projectCreateDirectory: z.boolean().optional(),
// COMPAT(projectList): added in v0.2.4, drop the gate when floor >= v0.2.4.
projectList: z.boolean().optional(),
// COMPAT(projectSuggestions): added in v0.14.1, remove gate after 2027-05-01.
projectSuggestions: z.boolean().optional(),
// COMPAT(commitsList): added in v0.1.110, remove gate after 2027-01-16.
commitsList: z.boolean().optional(),
// COMPAT(commitBaseClassification): added in v0.2.0, remove gate after 2027-01-23.
@@ -4498,6 +4510,24 @@ export const ProjectListResponseMessageSchema = z.object({
}),
});
export const ProjectSuggestionSchema = z.object({
// The main repository root for git checkouts and their worktrees; the session cwd otherwise.
rootPath: z.string(),
displayName: z.string(),
sessionCount: z.number().int().nonnegative(),
providerIds: z.array(z.string()),
lastActivityAt: z.string(),
});
export const ProjectSuggestionsResponseSchema = z.object({
type: z.literal("project.suggestions.response"),
payload: z.object({
requestId: z.string(),
suggestions: z.array(ProjectSuggestionSchema),
providerErrors: z.array(z.object({ provider: z.string(), message: z.string() })),
}),
});
export const ScriptStatusUpdateMessageSchema = z.object({
type: z.literal("script_status_update"),
payload: z.object({
@@ -7064,6 +7094,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
WorkspaceLabelDeleteInspectResponseSchema,
ProjectUpdateMessageSchema,
ProjectListResponseMessageSchema,
ProjectSuggestionsResponseSchema,
ScriptStatusUpdateMessageSchema,
WorkspaceSetupProgressMessageSchema,
WorkspaceSetupStatusResponseMessageSchema,
@@ -7428,6 +7459,9 @@ export type FetchRecentProviderSessionsRequestMessage = z.infer<
>;
export type FetchWorkspacesRequestMessage = z.infer<typeof FetchWorkspacesRequestMessageSchema>;
export type ProjectListRequestMessage = z.infer<typeof ProjectListRequestMessageSchema>;
export type ProjectSuggestionsRequest = z.infer<typeof ProjectSuggestionsRequestSchema>;
export type ProjectSuggestion = z.infer<typeof ProjectSuggestionSchema>;
export type ProjectSuggestionsResponse = z.infer<typeof ProjectSuggestionsResponseSchema>;
export type FetchAgentRequestMessage = z.infer<typeof FetchAgentRequestMessageSchema>;
export type AgentForkContextRequestMessage = z.infer<typeof AgentForkContextRequestMessageSchema>;
export type SendAgentMessageRequest = z.infer<typeof SendAgentMessageRequestSchema>;