mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
feat(plugin): add sidebar view examples and ref actions
Keep sidebar layout and gestures inside the plugin while routing entity handles to their owning hosts. Examples cover empty projects, pinned workspaces, task sections, nested agents and device-local label folders.
This commit is contained in:
@@ -393,7 +393,7 @@ describe("sidebar data SDK boundary", () => {
|
||||
it("accepts references and preserves string-id panel consumers", () => {
|
||||
expect(
|
||||
typeErrors(`
|
||||
import { useWorkspace, useAgent, type PluginClientContext, type PluginSidebarViewProps, type WorkspaceRef, type AgentRef, useProject, useTerminal, useWorkspaces, useAgents, useProjects, useTerminals, type ProjectRef, type TerminalRef, type Query, type Group } from "@getpaseo/plugin/client";
|
||||
import { useWorkspace, useAgent, usePaseo, type PluginClientContext, type PluginSidebarViewProps, type WorkspaceRef, type AgentRef, useProject, useTerminal, useWorkspaces, useAgents, useProjects, useTerminals, type ProjectRef, type TerminalRef, type Query, type Group } from "@getpaseo/plugin/client";
|
||||
import type { PluginWorkspaceSnapshot } from "@getpaseo/plugin";
|
||||
function Panel({ workspace, agent }: { workspace: WorkspaceRef; agent: AgentRef }) {
|
||||
const snapshot: PluginWorkspaceSnapshot | null = useWorkspace(workspace);
|
||||
@@ -405,6 +405,13 @@ describe("sidebar data SDK boundary", () => {
|
||||
const groups: readonly Group<AgentRef>[] = useAgents({ groupBy: item => item.status });
|
||||
const projects: readonly ProjectRef[] = useProjects({ hosts: [workspace.hostId] });
|
||||
const terminals: readonly TerminalRef[] = useTerminals();
|
||||
const paseo = usePaseo();
|
||||
void paseo.workspaces.ref(workspace).setPinned(true);
|
||||
void paseo.workspaces.ref(workspace).setLabel("Later", { assigned: true });
|
||||
void paseo.workspaces.ref("old-id").setTitle("Title");
|
||||
if (projects[0]) { paseo.projects.ref(projects[0]).open(); void paseo.projects.ref(projects[0]).createWorkspace(); }
|
||||
if (terminals[0]) { paseo.terminals.ref(terminals[0]).focus(); void paseo.terminals.ref(terminals[0]).kill(); }
|
||||
void paseo.agents.ref(agent);
|
||||
if (projects[0]) useProject(projects[0], item => item.displayName);
|
||||
if (terminals[0]) useTerminal(terminals[0]);
|
||||
function View(props: PluginSidebarViewProps) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { WorkspaceRef, AgentRef, ProjectRef, TerminalRef } from "../refs.js";
|
||||
import type { PluginText } from "../text.js";
|
||||
import type { ComponentType } from "react";
|
||||
import type { PaseoApi } from "@getpaseo/client";
|
||||
import type { PluginPaseoApi } from "./reference-api.js";
|
||||
import type { AgentTimelineItem, JsonValue } from "@getpaseo/protocol/agent-types";
|
||||
import type { ZodType, input as ZodInput, output as ZodOutput } from "zod";
|
||||
import type { PluginFile } from "./file.js";
|
||||
@@ -328,7 +328,7 @@ export interface PluginTimelineRendererContribution<Schema extends ZodType = Zod
|
||||
}
|
||||
|
||||
export interface PluginCommandCapabilities {
|
||||
paseo: PaseoApi;
|
||||
paseo: PluginPaseoApi;
|
||||
rpc<InputSchema extends ZodType, OutputSchema extends ZodType>(
|
||||
contract: PluginRpcContract<InputSchema, OutputSchema>,
|
||||
input: PluginWireInput<ZodInput<InputSchema>>,
|
||||
|
||||
@@ -38,3 +38,5 @@ export {
|
||||
export type { PluginLocaleSource } from "./translations.js";
|
||||
|
||||
export { createEntityRefs } from "./refs.js";
|
||||
|
||||
export { createPluginReferenceApi } from "./reference-api.js";
|
||||
|
||||
@@ -116,3 +116,5 @@ export { useTabState } from "./tab.js";
|
||||
|
||||
export type { WorkspaceRef, AgentRef, ProjectRef, TerminalRef } from "../refs.js";
|
||||
export type { Query, Group, GroupKey } from "./query.js";
|
||||
|
||||
export type { PluginPaseoApi, PluginProjectHandle, PluginTerminalHandle } from "./reference-api.js";
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import type { PaseoApi } from "@getpaseo/client";
|
||||
import type { PluginPaseoApi } from "./reference-api.js";
|
||||
import { createContext, useContext, type ReactNode } from "react";
|
||||
|
||||
const PaseoApiContext = createContext<PaseoApi | null>(null);
|
||||
const PaseoApiContext = createContext<PluginPaseoApi | null>(null);
|
||||
|
||||
export function usePaseoContextValue(): PaseoApi | null {
|
||||
export function usePaseoContextValue(): PluginPaseoApi | null {
|
||||
return useContext(PaseoApiContext);
|
||||
}
|
||||
|
||||
export function PaseoApiProvider({ children, paseo }: { children: ReactNode; paseo: PaseoApi }) {
|
||||
export function PaseoApiProvider({
|
||||
children,
|
||||
paseo,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
paseo: PluginPaseoApi;
|
||||
}) {
|
||||
return <PaseoApiContext.Provider value={paseo}>{children}</PaseoApiContext.Provider>;
|
||||
}
|
||||
|
||||
export function usePaseo(): PaseoApi {
|
||||
export function usePaseo(): PluginPaseoApi {
|
||||
const paseo = usePaseoContextValue();
|
||||
if (!paseo) throw new Error("usePaseo must run inside a contributed plugin surface");
|
||||
return paseo;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import type {
|
||||
PaseoApi,
|
||||
PaseoAgent,
|
||||
PaseoAgentHandle,
|
||||
PaseoWorkspace,
|
||||
PaseoWorkspaceHandle,
|
||||
PaseoWorkspaceCreateOptions,
|
||||
} from "@getpaseo/client";
|
||||
import type { PaseoTerminal, PaseoTerminalHandle } from "@getpaseo/client";
|
||||
import type { PluginProjectSnapshot } from "../contracts.js";
|
||||
import type { AgentRef, ProjectRef, TerminalRef, WorkspaceRef } from "../refs.js";
|
||||
import type { PluginSidebarViewProps } from "./contracts.js";
|
||||
|
||||
export interface PluginProjectHandle {
|
||||
readonly ref: ProjectRef;
|
||||
open(): void;
|
||||
createWorkspace(
|
||||
input?: Omit<PaseoWorkspaceCreateOptions, "source"> & {
|
||||
source?: PaseoWorkspaceCreateOptions["source"];
|
||||
},
|
||||
): Promise<PaseoWorkspaceHandle>;
|
||||
}
|
||||
export interface PluginTerminalHandle extends PaseoTerminalHandle {
|
||||
focus(): void;
|
||||
}
|
||||
export interface PluginPaseoApi extends PaseoApi {
|
||||
workspaces: Omit<PaseoApi["workspaces"], "ref"> & {
|
||||
ref(workspace: string | PaseoWorkspace | WorkspaceRef): PaseoWorkspaceHandle;
|
||||
};
|
||||
agents: Omit<PaseoApi["agents"], "ref"> & {
|
||||
ref(agent: string | PaseoAgent | AgentRef): PaseoAgentHandle;
|
||||
};
|
||||
projects: PaseoApi["projects"] & { ref(project: ProjectRef): PluginProjectHandle };
|
||||
terminals: Omit<PaseoApi["terminals"], "ref"> & {
|
||||
ref(terminal: TerminalRef): PluginTerminalHandle;
|
||||
ref(terminal: string | PaseoTerminal): PaseoTerminalHandle;
|
||||
};
|
||||
}
|
||||
|
||||
/** Host adapter; the installation retains its single API identity across every surface. */
|
||||
export function createPluginReferenceApi(input: {
|
||||
paseo: PaseoApi;
|
||||
resolveHost(hostId: string): PaseoApi;
|
||||
readProject(ref: ProjectRef): PluginProjectSnapshot | null;
|
||||
navigation: PluginSidebarViewProps["navigation"];
|
||||
}): PluginPaseoApi {
|
||||
const { paseo } = input;
|
||||
const workspaceRef = paseo.workspaces.ref;
|
||||
const agentRef = paseo.agents.ref;
|
||||
const terminalRef = paseo.terminals.ref;
|
||||
function refWorkspace(workspace: string | PaseoWorkspace | WorkspaceRef) {
|
||||
if (typeof workspace !== "string" && "hostId" in workspace)
|
||||
return input.resolveHost(workspace.hostId).workspaces.ref(workspace.id);
|
||||
return workspaceRef(workspace);
|
||||
}
|
||||
function refAgent(agent: string | PaseoAgent | AgentRef) {
|
||||
if (typeof agent !== "string" && "hostId" in agent)
|
||||
return input.resolveHost(agent.hostId).agents.ref(agent.id);
|
||||
return agentRef(agent);
|
||||
}
|
||||
function terminal(ref: TerminalRef): PluginTerminalHandle;
|
||||
function terminal(ref: string | PaseoTerminal): PaseoTerminalHandle;
|
||||
function terminal(ref: TerminalRef | string | PaseoTerminal): PaseoTerminalHandle {
|
||||
if (typeof ref !== "string" && "hostId" in ref) {
|
||||
const handle = input.resolveHost(ref.hostId).terminals.ref(ref.id);
|
||||
return Object.assign(handle, { focus: () => input.navigation.openTerminal(ref) });
|
||||
}
|
||||
return terminalRef(ref);
|
||||
}
|
||||
function project(ref: ProjectRef): PluginProjectHandle {
|
||||
return {
|
||||
ref,
|
||||
open: () => input.navigation.openProject(ref),
|
||||
async createWorkspace(options = {}) {
|
||||
const snapshot = input.readProject(ref);
|
||||
if (!snapshot) throw new Error("Project is no longer available");
|
||||
const source = options.source ?? { kind: "directory", path: snapshot.rootPath };
|
||||
return input.resolveHost(ref.hostId).workspaces.create({ ...options, source });
|
||||
},
|
||||
};
|
||||
}
|
||||
return Object.assign(paseo, {
|
||||
workspaces: Object.assign(paseo.workspaces, { ref: refWorkspace }),
|
||||
agents: Object.assign(paseo.agents, { ref: refAgent }),
|
||||
projects: Object.assign(paseo.projects, { ref: project }),
|
||||
terminals: Object.assign(paseo.terminals, { ref: terminal }),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user