Make local plugins manageable and debuggable (#3446)

* feat(plugins): expose Paseo API to local plugins

Plugin surfaces borrow the selected host connection while subprocess handlers receive an exclusively owned IPC session, so plugins can use normal workspace and agent operations without inventing another transport.

* feat(plugins): reload global enablement safely

Treat the global plugin switch as a live daemon setting and teach plugin automation to inspect it before acting. Enabling remains permission-gated because plugin backend code can access the daemon machine and client contributions execute inside the app.

* feat(plugins): expose plugin logs

Capture plugin stdout and stderr separately from IPC so authors can debug initialization, RPC, cleanup, and crash behavior from the app or CLI. Keep a bounded tail across lifecycle recovery while preserving structured output in the daemon log.

* fix(build): order plugin dependency builds

* test(plugins): align startup and CLI expectations
This commit is contained in:
Mohamed Boudra
2026-08-16 18:54:44 +08:00
committed by GitHub
parent d99258cdd0
commit 16120ebff1
62 changed files with 2945 additions and 202 deletions
+2
View File
@@ -28,6 +28,7 @@
"test": "vitest run"
},
"devDependencies": {
"@getpaseo/client": "0.4.0",
"@types/node": "^20.9.0",
"@types/react": "~19.2.0",
"react": "19.1.0",
@@ -36,6 +37,7 @@
"zod": "^4.4.3"
},
"peerDependencies": {
"@getpaseo/client": "0.4.0",
"react": "19.1.0",
"zod": "^4.4.3"
}
+7 -1
View File
@@ -1,4 +1,5 @@
import type { ComponentType } from "react";
import type { PaseoApi } from "@getpaseo/client";
import type { ZodType, input as ZodInput, output as ZodOutput } from "zod";
import type { PluginRpcContract } from "./rpc.js";
@@ -35,11 +36,16 @@ export interface PluginAttachmentSourceContribution {
search: PluginRpcContract;
}
export interface PluginHandlerContext {
paseo: PaseoApi;
}
export interface PluginContext {
handle<InputSchema extends ZodType, OutputSchema extends ZodType>(
contract: PluginRpcContract<InputSchema, OutputSchema>,
handler: (
input: ZodOutput<InputSchema>,
context: PluginHandlerContext,
) => ZodInput<OutputSchema> | Promise<ZodInput<OutputSchema>>,
): void;
addSurface(id: string, Component: ComponentType<PluginSurfaceProps>): void;
@@ -47,6 +53,6 @@ export interface PluginContext {
addAttachmentSource(contribution: PluginAttachmentSourceContribution): void;
}
export type PluginCleanup = () => void;
export type PluginCleanup = () => void | Promise<void>;
export type PluginContribution = (plugin: PluginContext) => PluginCleanup;
+2 -1
View File
@@ -7,6 +7,7 @@ import type {
PluginSurfaceProps,
} from "./contracts.js";
import { PluginRpcProvider } from "./rpc-context.js";
import { PaseoApiProvider } from "./paseo-context.js";
import { callPluginRpc } from "./rpc.js";
import type { ComponentType } from "react";
@@ -47,4 +48,4 @@ export async function searchPluginAttachments(
return PluginAttachmentSearchPayloadSchema.parseAsync(output);
}
export { callPluginRpc, PluginRpcProvider };
export { callPluginRpc, PaseoApiProvider, PluginRpcProvider };
+2
View File
@@ -9,10 +9,12 @@ export type {
PluginCleanup,
PluginContribution,
PluginContext,
PluginHandlerContext,
PluginSidebarContribution,
PluginSurfaceContribution,
PluginSurfaceProps,
} from "./contracts.js";
export { usePaseo } from "./paseo-context.js";
import type { PluginAttachmentSourceContribution } from "./contracts.js";
export { useRpc } from "./rpc-context.js";
export { defineRpc, type PluginRpcContract } from "./rpc.js";
+14
View File
@@ -0,0 +1,14 @@
import type { PaseoApi } from "@getpaseo/client";
import { createContext, useContext, type ReactNode } from "react";
const PaseoApiContext = createContext<PaseoApi | null>(null);
export function PaseoApiProvider({ children, paseo }: { children: ReactNode; paseo: PaseoApi }) {
return <PaseoApiContext.Provider value={paseo}>{children}</PaseoApiContext.Provider>;
}
export function usePaseo(): PaseoApi {
const paseo = useContext(PaseoApiContext);
if (!paseo) throw new Error("usePaseo must run inside a contributed plugin surface");
return paseo;
}