From 4de913fb82d2c6f433b1291a1870281ee4a0eacb Mon Sep 17 00:00:00 2001 From: Omer Cohen <639682+omercnet@users.noreply.github.com> Date: Wed, 26 Aug 2026 23:57:22 +0300 Subject: [PATCH] fix(server): plugin daemon sessions see all providers by pinning the daemon appVersion (#3902) * fix(server): expose all providers to plugin sessions * fix(app): route workspace actions semantically (#3896) * fix(app): route workspace actions semantically Separate Explorer visibility from view navigation, and give composer changes and pull request opens semantic routing owners. Unify diff destinations, keep compact PR navigation in Explorer, and derive maximize availability from visible workspace panes. * test(app): cover composer changes routing Update the browser journey for the Explorer-first composer action, then assert the second press follows the shared diff destination. * test(server): track plugin runtime dependency parameter --------- Co-authored-by: Mohamed Boudra Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- packages/server/src/server/bootstrap.ts | 2 +- .../src/server/plugins/index.posix.test.ts | 22 ++++++++++++------- packages/server/src/server/plugins/index.ts | 3 ++- .../plugins/plugin-paseo-api.e2e.test.ts | 21 ++++++++++++++++-- .../server/plugins/plugin-process-protocol.ts | 2 +- .../src/server/plugins/plugin-process.ts | 1 + .../src/server/plugins/runtime.posix.test.ts | 4 ++-- packages/server/src/server/plugins/runtime.ts | 7 +++++- .../server/test-utils/fake-agent-client.ts | 7 ++++++ 9 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/server/src/server/bootstrap.ts b/packages/server/src/server/bootstrap.ts index 50d32b9c1b..3033be77d5 100644 --- a/packages/server/src/server/bootstrap.ts +++ b/packages/server/src/server/bootstrap.ts @@ -609,7 +609,7 @@ export async function createPaseoDaemon( }); const browserToolsPolicy = new DaemonConfigBrowserToolsPolicy(daemonConfigStore); const browserToolsBroker = new BrowserToolsBroker({}); - const pluginRuntime = new PluginService(logger, daemonConfigStore); + const pluginRuntime = new PluginService(logger, daemonConfigStore, daemonVersion); const serverId = getOrCreateServerId(config.paseoHome, { logger }); const daemonKeyPair = await loadOrCreateDaemonKeyPair(config.paseoHome, logger); diff --git a/packages/server/src/server/plugins/index.posix.test.ts b/packages/server/src/server/plugins/index.posix.test.ts index fc5c83d12e..755ddb054d 100644 --- a/packages/server/src/server/plugins/index.posix.test.ts +++ b/packages/server/src/server/plugins/index.posix.test.ts @@ -36,10 +36,10 @@ function createStore( function createService( home: string, plugins: Record = {}, - runtime?: ConstructorParameters[2], + runtime?: ConstructorParameters[3], ): PluginService { return bindTestSessionHost( - new PluginService(pino({ level: "silent" }), createStore(home, plugins), runtime), + new PluginService(pino({ level: "silent" }), createStore(home, plugins), "0.4.0", runtime), ); } @@ -225,7 +225,9 @@ describe("PluginService", () => { `export default function contribute(plugin: unknown) { void plugin; return () => undefined; }`, ); const store = createStore(home); - const service = bindTestSessionHost(new PluginService(pino({ level: "silent" }), store)); + const service = bindTestSessionHost( + new PluginService(pino({ level: "silent" }), store, "0.4.0"), + ); await service.start(); await expect( @@ -266,7 +268,9 @@ export default function contribute(plugin: unknown) { }`, ); const store = createStore(home); - const service = bindTestSessionHost(new PluginService(pino({ level: "silent" }), store)); + const service = bindTestSessionHost( + new PluginService(pino({ level: "silent" }), store, "0.4.0"), + ); await service.start(); await service.installDirectory({ path: directory }); @@ -293,7 +297,9 @@ export default function contribute(plugin: unknown) { `export default function contribute(plugin: unknown) { void plugin; return () => undefined; }`, ); const store = createStore(home); - const service = bindTestSessionHost(new PluginService(pino({ level: "silent" }), store)); + const service = bindTestSessionHost( + new PluginService(pino({ level: "silent" }), store, "0.4.0"), + ); await service.start(); await service.installDirectory({ path: first }); await service.installDirectory({ path: second }); @@ -321,7 +327,7 @@ export default function contribute(plugin: unknown) { }); store.patch({ pluginsEnabled: false }); const paused = createPausedRuntime(); - const service = new PluginService(pino({ level: "silent" }), store, paused.runtime); + const service = new PluginService(pino({ level: "silent" }), store, "0.4.0", paused.runtime); await service.start(); store.patch({ pluginsEnabled: true }); @@ -343,7 +349,7 @@ export default function contribute(plugin: unknown) { slow: { source: "directory", path: "/plugins/slow", enabled: false }, }); const paused = createPausedRuntime(); - const service = new PluginService(pino({ level: "silent" }), store, paused.runtime); + const service = new PluginService(pino({ level: "silent" }), store, "0.4.0", paused.runtime); await service.start(); const enabling = expect(service.enablePlugin("slow")).rejects.toThrow( @@ -366,7 +372,7 @@ export default function contribute(plugin: unknown) { slow: { source: "directory", path: "/plugins/slow", enabled: false }, }); const paused = createPluginSelectivePausedRuntime("occupier"); - const service = new PluginService(pino({ level: "silent" }), store, paused.runtime); + const service = new PluginService(pino({ level: "silent" }), store, "0.4.0", paused.runtime); await service.start(); const occupying = service.enablePlugin("occupier"); diff --git a/packages/server/src/server/plugins/index.ts b/packages/server/src/server/plugins/index.ts index 4b3d5dd9e6..513531b34f 100644 --- a/packages/server/src/server/plugins/index.ts +++ b/packages/server/src/server/plugins/index.ts @@ -34,7 +34,8 @@ export class PluginService { constructor( logger: pino.Logger, private readonly configStore: DaemonConfigStore, - runtime: PluginRuntimePort = new PluginRuntime(logger), + daemonVersion: string, + runtime: PluginRuntimePort = new PluginRuntime(logger, daemonVersion), ) { this.logger = logger.child({ module: "plugin-service" }); this.runtime = runtime; diff --git a/packages/server/src/server/plugins/plugin-paseo-api.e2e.test.ts b/packages/server/src/server/plugins/plugin-paseo-api.e2e.test.ts index a9e3fe3560..d01ff4d411 100644 --- a/packages/server/src/server/plugins/plugin-paseo-api.e2e.test.ts +++ b/packages/server/src/server/plugins/plugin-paseo-api.e2e.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { afterEach, expect, test } from "vitest"; import { DaemonClient } from "../test-utils/daemon-client.js"; import { createTestPaseoDaemon } from "../test-utils/paseo-daemon.js"; +import { createTestAgentClient, createTestAgentClients } from "../test-utils/fake-agent-client.js"; const roots: string[] = []; @@ -30,6 +31,12 @@ const create = defineRpc({ output: z.object({ workspaceId: z.string(), agentId: z.string() }), }); +const list = defineRpc({ + name: "list", + input: z.object({}), + output: z.object({ agentIds: z.array(z.string()) }), +}); + export default function contribute(plugin: PluginContext) { plugin.handle(create, async ({ path }, { paseo }) => { const workspace = await paseo.workspaces.create({ @@ -37,16 +44,22 @@ export default function contribute(plugin: PluginContext) { title: "Plugin workspace", }); const agent = await workspace.agents.create({ - config: { provider: "codex/test" }, + config: { provider: "pi/test" }, prompt: "Created by a plugin handler", }); return { workspaceId: workspace.id, agentId: agent.id }; }); + plugin.handle(list, async (_input, { paseo }) => { + const result = await paseo.agents.list({ page: { limit: 100 } }); + return { agentIds: result.entries.map((entry) => entry.agent.id) }; + }); return () => undefined; }`, ); - const daemon = await createTestPaseoDaemon(); + const daemon = await createTestPaseoDaemon({ + agentClients: { ...createTestAgentClients(), pi: createTestAgentClient("pi") }, + }); const client = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws`, appVersion: "0.4.0", @@ -71,6 +84,10 @@ export default function contribute(plugin: PluginContext) { if (typeof created !== "object" || created === null) { throw new Error("Plugin returned an invalid creation result"); } + const listed = await client.invokePluginRpc("paseo-api", "list", {}); + expect(listed).toEqual({ + agentIds: expect.arrayContaining([Reflect.get(created, "agentId")]), + }); await client.removePlugin("paseo-api"); const workspaces = await client.fetchWorkspaces(); const agents = await client.fetchAgents(); diff --git a/packages/server/src/server/plugins/plugin-process-protocol.ts b/packages/server/src/server/plugins/plugin-process-protocol.ts index 2d953bd9a9..c9d61336c0 100644 --- a/packages/server/src/server/plugins/plugin-process-protocol.ts +++ b/packages/server/src/server/plugins/plugin-process-protocol.ts @@ -1,5 +1,5 @@ export type PluginProcessRequest = - | { type: "initialize"; pluginId: string; bundle: string } + | { type: "initialize"; pluginId: string; bundle: string; appVersion: string } | { type: "invoke"; requestId: string; method: string; input: unknown } | { type: "shutdown" } | { type: "paseo_frame"; data: string | Uint8Array; isBinary: boolean } diff --git a/packages/server/src/server/plugins/plugin-process.ts b/packages/server/src/server/plugins/plugin-process.ts index 7fe29aef26..89c848f957 100644 --- a/packages/server/src/server/plugins/plugin-process.ts +++ b/packages/server/src/server/plugins/plugin-process.ts @@ -99,6 +99,7 @@ async function initialize(message: Extract[1]> = {}, + dependencies: NonNullable[2]> = {}, logger = pino({ level: "silent" }), ): PluginRuntime { - return new PluginRuntime(logger, { + return new PluginRuntime(logger, "0.4.0", { ...dependencies, sessionHost: dependencies.sessionHost ?? { async attachPluginSocket(_pluginId, socket) { diff --git a/packages/server/src/server/plugins/runtime.ts b/packages/server/src/server/plugins/runtime.ts index f786dbed52..c6edb0bb7c 100644 --- a/packages/server/src/server/plugins/runtime.ts +++ b/packages/server/src/server/plugins/runtime.ts @@ -204,7 +204,11 @@ export class PluginRuntime { private sessionHost: PluginPaseoSessionHost | null; private readonly listeners = new Set<(pluginId: string, error?: string) => void>(); - constructor(logger: pino.Logger, dependencies: PluginRuntimeDependencies = {}) { + constructor( + logger: pino.Logger, + private readonly daemonVersion: string, + dependencies: PluginRuntimeDependencies = {}, + ) { this.logger = logger.child({ module: "plugins" }); this.spawnChild = dependencies.spawnChild ?? spawnPluginChild; this.sessionHost = dependencies.sessionHost ?? null; @@ -363,6 +367,7 @@ export class PluginRuntime { void send(child, { type: "initialize", pluginId, + appVersion: this.daemonVersion, bundle: bundles.serverBundle, }).catch(fail); }); diff --git a/packages/server/src/server/test-utils/fake-agent-client.ts b/packages/server/src/server/test-utils/fake-agent-client.ts index 2b60ab424d..4d16f0302e 100644 --- a/packages/server/src/server/test-utils/fake-agent-client.ts +++ b/packages/server/src/server/test-utils/fake-agent-client.ts @@ -1281,3 +1281,10 @@ export function createTestAgentClients( opencode: new FakeAgentClient("opencode", options), }; } + +export function createTestAgentClient( + provider: string, + options: TestAgentClientOptions = {}, +): AgentClient { + return new FakeAgentClient(provider, options); +}