mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 05:02:58 +00:00
feat(client): expose project list in public SDK (#3899)
This commit is contained in:
+2
-2
@@ -164,8 +164,8 @@ RPC contracts validate inputs and outputs in both the app and plugin subprocess.
|
||||
typed async function. Use the host-provided `@tanstack/react-query` for request state and caching;
|
||||
Paseo gives each plugin installation its own query client.
|
||||
|
||||
`usePaseo()` and the handler's `{ paseo }` context expose the same `PaseoApi`: workspaces, agents,
|
||||
providers, and daemon config. They do not expose connection lifecycle. A surface borrows the
|
||||
`usePaseo()` and the handler's `{ paseo }` context expose the same `PaseoApi`: projects,
|
||||
workspaces, agents, providers, and daemon config. They do not expose connection lifecycle. A surface borrows the
|
||||
selected host's existing connection; switching the screen's host changes both `usePaseo()` and
|
||||
`useRpc()` to that host. An offline selected host fails there and never falls through to another
|
||||
installation. A server handler owns an IPC-backed daemon session for the life of its subprocess.
|
||||
|
||||
@@ -63,6 +63,7 @@ function parseSentSessionMessage(data: string | ArrayBuffer | Uint8Array | undef
|
||||
draftConfig?: unknown;
|
||||
filter?: unknown;
|
||||
page?: unknown;
|
||||
sync?: unknown;
|
||||
text?: string;
|
||||
} {
|
||||
if (typeof data !== "string") {
|
||||
@@ -224,12 +225,84 @@ test("createPaseoApi borrows daemon capabilities without exposing connection own
|
||||
|
||||
const paseo = createPaseoApi(daemonClient);
|
||||
|
||||
expect(Object.keys(paseo).sort()).toEqual(["agents", "config", "providers", "workspaces"]);
|
||||
expect(Object.keys(paseo).sort()).toEqual([
|
||||
"agents",
|
||||
"config",
|
||||
"projects",
|
||||
"providers",
|
||||
"workspaces",
|
||||
]);
|
||||
expect("connect" in paseo).toBe(false);
|
||||
expect("close" in paseo).toBe(false);
|
||||
expect("skills" in paseo.agents).toBe(false);
|
||||
});
|
||||
|
||||
test("project actions list registered projects through the existing RPC", async () => {
|
||||
const { client, ws } = await connectClient();
|
||||
|
||||
const listPromise = client.projects.list({
|
||||
requestId: "projects-list-request",
|
||||
sync: { generation: "daemon-generation", afterSeq: 7 },
|
||||
});
|
||||
expect(parseSentSessionMessage(ws.sent.at(-1))).toMatchObject({
|
||||
type: "project.list.request",
|
||||
requestId: "projects-list-request",
|
||||
sync: { generation: "daemon-generation", afterSeq: 7 },
|
||||
});
|
||||
|
||||
ws.message(
|
||||
sessionMessage({
|
||||
type: "project.list.response",
|
||||
payload: {
|
||||
requestId: "projects-list-request",
|
||||
projects: [
|
||||
{
|
||||
projectId: "project_sdk",
|
||||
projectKey: "sdk",
|
||||
projectDisplayName: "SDK",
|
||||
projectCustomName: null,
|
||||
projectCustomIconRevision: null,
|
||||
projectIconRevision: "icon-revision",
|
||||
projectRootPath: "/repo/sdk",
|
||||
projectKind: "git",
|
||||
syncSeq: 8,
|
||||
},
|
||||
],
|
||||
sync: {
|
||||
generation: "daemon-generation",
|
||||
headSeq: 8,
|
||||
mode: "changes",
|
||||
removals: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(listPromise).resolves.toEqual({
|
||||
requestId: "projects-list-request",
|
||||
projects: [
|
||||
{
|
||||
projectId: "project_sdk",
|
||||
projectKey: "sdk",
|
||||
projectDisplayName: "SDK",
|
||||
projectCustomName: null,
|
||||
projectCustomIconRevision: null,
|
||||
projectIconRevision: "icon-revision",
|
||||
projectRootPath: "/repo/sdk",
|
||||
projectKind: "git",
|
||||
syncSeq: 8,
|
||||
},
|
||||
],
|
||||
sync: {
|
||||
generation: "daemon-generation",
|
||||
headSeq: 8,
|
||||
mode: "changes",
|
||||
removals: [],
|
||||
},
|
||||
});
|
||||
await client.close();
|
||||
});
|
||||
|
||||
test("agent actions list the daemon directory without exposing the low-level client", async () => {
|
||||
const { client, ws } = await connectClient();
|
||||
const listedAgent = createAgent({ title: "Planner" });
|
||||
|
||||
@@ -8,11 +8,14 @@ import type {
|
||||
ListProviderFeaturesRequestMessage,
|
||||
ListProviderFeaturesResponseMessage,
|
||||
ListProviderModelsResponseMessage,
|
||||
ProjectListRequestMessage,
|
||||
ProjectListResponseMessage,
|
||||
ListProviderModesResponseMessage,
|
||||
MutableDaemonConfig,
|
||||
MutableDaemonConfigPatch,
|
||||
ProviderDiagnosticResponseMessage,
|
||||
ProjectPlacementPayload,
|
||||
WorkspaceProjectDescriptorPayload,
|
||||
RefreshProvidersSnapshotResponseMessage,
|
||||
SendAgentMessageRequest,
|
||||
SessionOutboundMessage,
|
||||
@@ -77,6 +80,11 @@ export interface PaseoClientConfig {
|
||||
export type PaseoWorkspace = WorkspaceDescriptorPayload;
|
||||
export type PaseoAgent = AgentSnapshotPayload;
|
||||
export type PaseoAgentListOptions = FetchAgentsOptions;
|
||||
export type PaseoProject = WorkspaceProjectDescriptorPayload;
|
||||
export type PaseoProjectListOptions = Omit<ProjectListRequestMessage, "type" | "requestId"> & {
|
||||
requestId?: string;
|
||||
};
|
||||
export type PaseoProjectListResult = ProjectListResponseMessage["payload"];
|
||||
|
||||
export interface PaseoAgentListResult {
|
||||
requestId: string;
|
||||
@@ -143,6 +151,10 @@ export interface PaseoWorkspaceHandle {
|
||||
subscribe(handler: (update: PaseoWorkspaceUpdate) => void): () => void;
|
||||
}
|
||||
|
||||
export interface PaseoProjectActions {
|
||||
list(options?: PaseoProjectListOptions): Promise<PaseoProjectListResult>;
|
||||
}
|
||||
|
||||
export interface PaseoWorkspaceActions {
|
||||
list(options?: PaseoWorkspaceListOptions): Promise<PaseoWorkspaceListResult>;
|
||||
ref(workspace: string | PaseoWorkspace): PaseoWorkspaceHandle;
|
||||
@@ -356,6 +368,7 @@ export interface PaseoConfigActions {
|
||||
|
||||
export interface PaseoApi {
|
||||
readonly workspaces: PaseoWorkspaceActions;
|
||||
readonly projects: PaseoProjectActions;
|
||||
readonly agents: PaseoAgentActions;
|
||||
readonly providers: PaseoProviderActions;
|
||||
readonly config: PaseoConfigActions;
|
||||
@@ -412,6 +425,9 @@ export function createPaseoApi(daemonClient: DaemonClient): PaseoApi {
|
||||
const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent);
|
||||
|
||||
return {
|
||||
projects: {
|
||||
list: (options) => daemonClient.listProjects(options),
|
||||
},
|
||||
workspaces: {
|
||||
list: (options) => daemonClient.fetchWorkspaces(options),
|
||||
ref: (workspace) => createWorkspaceHandle(workspace),
|
||||
|
||||
@@ -445,7 +445,7 @@ function PullRequestAction() {
|
||||
}
|
||||
```
|
||||
|
||||
The returned API covers workspaces, agents, providers, and daemon config. See the [SDK API reference](/docs/sdk/reference) for its methods. Connection lifecycle methods are intentionally absent because Paseo owns the connection.
|
||||
The returned API covers projects, workspaces, agents, providers, and daemon config. See the [SDK API reference](/docs/sdk/reference) for its methods. Connection lifecycle methods are intentionally absent because Paseo owns the connection.
|
||||
|
||||
## Add plugin-specific backend behavior
|
||||
|
||||
|
||||
@@ -95,6 +95,12 @@ Creation options include `config`, `cwd`, `parent`, `title`, `prompt`, `env`, `o
|
||||
|
||||
`agent.timeline.subscribe(handler)` listens for stream events belonging to the agent and returns a local unsubscribe function.
|
||||
|
||||
## `client.projects`
|
||||
|
||||
| Method | Result | Behavior |
|
||||
| ---------------- | ------------------------ | ----------------------------------------------------------------------------- |
|
||||
| `list(options?)` | `PaseoProjectListResult` | Lists every registered project, including projects with no active workspaces. |
|
||||
|
||||
## `client.workspaces`
|
||||
|
||||
| Method | Result | Behavior |
|
||||
|
||||
Reference in New Issue
Block a user