Add a keyboard-driven project setup flow (#2097)
* feat(projects): add keyboard-driven project setup * test(app): update add project loading assertion * fix(projects): harden GitHub project setup
This commit is contained in:
@@ -2258,6 +2258,136 @@ test("sends project.add.request without creating a workspace", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("searches GitHub repositories through the dotted RPC", async () => {
|
||||
const logger = createMockLogger();
|
||||
const mock = createMockTransport();
|
||||
const client = new DaemonClient({
|
||||
url: "ws://test",
|
||||
clientId: "clsk_unit_test",
|
||||
logger,
|
||||
reconnect: { enabled: false },
|
||||
transportFactory: () => mock.transport,
|
||||
});
|
||||
clients.push(client);
|
||||
|
||||
const connectPromise = client.connect();
|
||||
mock.triggerOpen();
|
||||
await connectPromise;
|
||||
|
||||
const searchPromise = client.searchGithubRepositories(
|
||||
{ query: "paseo", limit: 10 },
|
||||
"req-repositories",
|
||||
);
|
||||
expect(parseSentFrame(mock.sent[0])).toEqual({
|
||||
type: "workspace.github.search_repositories.request",
|
||||
query: "paseo",
|
||||
limit: 10,
|
||||
requestId: "req-repositories",
|
||||
});
|
||||
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "workspace.github.search_repositories.response",
|
||||
payload: {
|
||||
status: "success",
|
||||
requestId: "req-repositories",
|
||||
repositories: [
|
||||
{
|
||||
id: "R_paseo",
|
||||
name: "paseo",
|
||||
nameWithOwner: "getpaseo/paseo",
|
||||
description: "Development environment in your pocket",
|
||||
visibility: "public",
|
||||
updatedAt: "2026-07-15T10:00:00Z",
|
||||
cloneUrl: "git@github.com:getpaseo/paseo.git",
|
||||
},
|
||||
],
|
||||
available: true,
|
||||
error: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(searchPromise).resolves.toEqual({
|
||||
status: "success",
|
||||
requestId: "req-repositories",
|
||||
repositories: [
|
||||
{
|
||||
id: "R_paseo",
|
||||
name: "paseo",
|
||||
nameWithOwner: "getpaseo/paseo",
|
||||
description: "Development environment in your pocket",
|
||||
visibility: "public",
|
||||
updatedAt: "2026-07-15T10:00:00Z",
|
||||
cloneUrl: "git@github.com:getpaseo/paseo.git",
|
||||
},
|
||||
],
|
||||
available: true,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("creates and registers a project directory through the dotted RPC", async () => {
|
||||
const logger = createMockLogger();
|
||||
const mock = createMockTransport();
|
||||
const client = new DaemonClient({
|
||||
url: "ws://test",
|
||||
clientId: "clsk_unit_test",
|
||||
logger,
|
||||
reconnect: { enabled: false },
|
||||
transportFactory: () => mock.transport,
|
||||
});
|
||||
clients.push(client);
|
||||
|
||||
const connectPromise = client.connect();
|
||||
mock.triggerOpen();
|
||||
await connectPromise;
|
||||
|
||||
const createPromise = client.createProjectDirectory(
|
||||
{ parentPath: "/tmp/projects", name: "new-project" },
|
||||
"req-create-project-directory",
|
||||
);
|
||||
expect(parseSentFrame(mock.sent[0])).toEqual({
|
||||
type: "project.create_directory.request",
|
||||
parentPath: "/tmp/projects",
|
||||
name: "new-project",
|
||||
requestId: "req-create-project-directory",
|
||||
});
|
||||
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "project.create_directory.response",
|
||||
payload: {
|
||||
requestId: "req-create-project-directory",
|
||||
directoryPath: "/tmp/projects/new-project",
|
||||
project: {
|
||||
projectId: "directory:/tmp/projects/new-project",
|
||||
projectDisplayName: "new-project",
|
||||
projectCustomName: null,
|
||||
projectRootPath: "/tmp/projects/new-project",
|
||||
projectKind: "non_git",
|
||||
},
|
||||
error: null,
|
||||
errorCode: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(createPromise).resolves.toEqual({
|
||||
requestId: "req-create-project-directory",
|
||||
directoryPath: "/tmp/projects/new-project",
|
||||
project: {
|
||||
projectId: "directory:/tmp/projects/new-project",
|
||||
projectDisplayName: "new-project",
|
||||
projectCustomName: null,
|
||||
projectRootPath: "/tmp/projects/new-project",
|
||||
projectKind: "non_git",
|
||||
},
|
||||
error: null,
|
||||
errorCode: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("sends first-agent prompt context with workspace.create.request", async () => {
|
||||
const logger = createMockLogger();
|
||||
const mock = createMockTransport();
|
||||
|
||||
@@ -55,7 +55,9 @@ import type {
|
||||
PaseoWorktreeArchiveResponse,
|
||||
ProjectIconResponse,
|
||||
ProjectAddResponse,
|
||||
ProjectCreateDirectoryResponse,
|
||||
OpenProjectResponseMessage,
|
||||
WorkspaceGithubSearchRepositoriesResponse,
|
||||
WorkspaceGithubCloneProtocol,
|
||||
WorkspaceGithubCloneResponse,
|
||||
ArchiveWorkspaceResponseMessage,
|
||||
@@ -763,6 +765,9 @@ export interface RenameTerminalInput {
|
||||
}
|
||||
type OpenProjectPayload = OpenProjectResponseMessage["payload"];
|
||||
type ProjectAddPayload = ProjectAddResponse["payload"];
|
||||
export type ProjectCreateDirectoryPayload = ProjectCreateDirectoryResponse["payload"];
|
||||
export type WorkspaceGithubSearchRepositoriesPayload =
|
||||
WorkspaceGithubSearchRepositoriesResponse["payload"];
|
||||
type WorkspaceGithubClonePayload = WorkspaceGithubCloneResponse["payload"];
|
||||
type ArchiveWorkspacePayload = ArchiveWorkspaceResponseMessage["payload"];
|
||||
type WorkspaceSetupStatusPayload = WorkspaceSetupStatusResponseMessage["payload"];
|
||||
@@ -1994,6 +1999,36 @@ export class DaemonClient {
|
||||
});
|
||||
}
|
||||
|
||||
async createProjectDirectory(
|
||||
input: { parentPath: string; name: string },
|
||||
requestId?: string,
|
||||
): Promise<ProjectCreateDirectoryPayload> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"project.create_directory.response">({
|
||||
requestId,
|
||||
message: {
|
||||
type: "project.create_directory.request",
|
||||
parentPath: input.parentPath,
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async searchGithubRepositories(
|
||||
input: { query: string; limit?: number },
|
||||
requestId?: string,
|
||||
): Promise<WorkspaceGithubSearchRepositoriesPayload> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"workspace.github.search_repositories.response">(
|
||||
{
|
||||
requestId,
|
||||
message: {
|
||||
type: "workspace.github.search_repositories.request",
|
||||
query: input.query,
|
||||
limit: input.limit,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async cloneGithubWorkspace(
|
||||
input: { repo: string; targetDirectory: string; cloneProtocol?: WorkspaceGithubCloneProtocol },
|
||||
requestId?: string,
|
||||
|
||||
Reference in New Issue
Block a user