From d3e16332819b6e3e53696a7be88f8533d1cbd1cb Mon Sep 17 00:00:00 2001 From: aMgLn Date: Fri, 11 Sep 2026 14:31:42 +0900 Subject: [PATCH] Migrate Paseo plugin to v0.8's split client/server/shared architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the real, user-reported breakage: after updating Paseo to 0.8.0, the installed sessionforge plugin failed to load with "Plugin \"sessionforge\" requires Paseo <0.8.0. [...] Ask its author to migrate it." Reproduced the exact same error locally against a real Paseo 0.8.0 daemon before starting. Fetched the current v0.8 migration guide and reference docs (not relying on memory, since this is a real breaking API change) to get the actual new module shapes right: - paseo-plugin.json: added requirements.paseo: ">=0.8.0" (missing the field at all defaults to "<0.8.0", which is exactly what broke) - Split the old single index.ts into index.client.tsx (surface/sidebar/ command-center registration) and index.server.ts (RPC handler registration + the background-rescan cleanup hook) - Moved main.client.tsx -> client/sessions.tsx, src/server/session-handlers. server.ts -> server/session-handlers.ts, src/server/session-contracts. shared.ts -> shared/session-contracts.ts, per the new "the directory is the boundary" rule - Updated imports for the new module split: useRpc/PluginSurfaceProps now from @getpaseo/plugin/client (were @getpaseo/plugin); defineRpc now from the shared root @getpaseo/plugin (was @getpaseo/plugin/server) - Rewrote paseo-plugin.d.ts's hand-written ambient type declarations to match the new @getpaseo/plugin / @getpaseo/plugin/client / @getpaseo/plugin/server module split (there's no real installed @getpaseo/plugin package here to pull types from — Paseo supplies the real runtime at install time, this project has always hand-declared its own types against it) - Updated scripts/package-plugin.mjs's staged file list for the new layout Also discovered while testing this for real: Paseo 0.8's build step now scans the *entire* given plugin directory and rejects any file outside client/server/shared — including this monorepo's own unrelated packages/cli/dist/**. That means the previously-documented "point paseo straight at the repo root for local dev" flow no longer works at all here; only the packaged, self-contained bundle from `npm run package:plugin` does. Updated docs/MANUAL.md and CONTRIBUTING.md to document the actual working dev loop (package:plugin, then install/reload against build/paseo-plugin, not the repo root). Verified for real against the actual installed Paseo 0.8.0 daemon, not just a clean typecheck: installed the packaged bundle standalone (paseo plugin ls reports "running", clean logs), then used it to fix the actual broken "sessionforge" registration on this machine (same daemon that produced the original error), and separately verified the edit -> repackage -> reload dev loop actually picks up source changes. Also fixed an unrelated but real test-fragility bug found along the way: discover.server.test.ts's "keep-session" fixture used a hardcoded absolute date instead of one relative to "now", so it silently flipped from KEEP to ARCHIVE (crossing classifySession's 14-day threshold) as real time passed, breaking 3 tests that depended on it. Made the fixture dates relative to test-run time instead, so this can't happen again. --- CONTRIBUTING.md | 12 ++ main.client.tsx => client/sessions.tsx | 6 +- docs/MANUAL.md | 21 ++- docs/REFERENCE.md | 41 +++-- index.client.tsx | 18 +++ index.server.ts | 45 ++++++ index.ts | 55 ------- packages/cli/src/core/discover.server.test.ts | 14 +- paseo-plugin.d.ts | 142 +++++++++++------- paseo-plugin.json | 3 +- scripts/package-plugin.mjs | 27 +++- .../session-handlers.ts | 0 .../session-contracts.ts | 2 +- 13 files changed, 238 insertions(+), 148 deletions(-) rename main.client.tsx => client/sessions.tsx (99%) create mode 100644 index.client.tsx create mode 100644 index.server.ts delete mode 100644 index.ts rename src/server/session-handlers.server.ts => server/session-handlers.ts (100%) rename src/server/session-contracts.shared.ts => shared/session-contracts.ts (98%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aecb3a6..ffb75d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,13 @@ build output, not the raw source. `postinstall` handles this on a fresh `npm ins edits made after that without a fresh build. (This exact ordering mistake broke CI once already — see `.github/workflows/ci.yml`'s comments.) +**If you're changing the Paseo plugin itself** (`index.client.tsx`, `index.server.ts`, `client/`, `server/`, +`shared/`), pointing `paseo plugin install`/`reload` straight at the repo root doesn't work under Paseo +0.8+ — its build step scans the whole given directory and rejects `packages/cli`'s own files as not +belonging to `client/`/`server/`/`shared/`. Use `npm run package:plugin` (stages a self-contained copy into +`build/paseo-plugin`, with no sibling-package contamination) and point `paseo` at that instead — see +[`docs/MANUAL.md`](docs/MANUAL.md#installing-the-paseo-plugin) for the exact dev-loop commands. + ## CI `.github/workflows/ci.yml` runs on every push to `main` and every PR: `build` → `typecheck` → `test` → a @@ -86,6 +93,11 @@ workflow's release tag format if either changes. considering it done, not just synthetic fixtures — see `packages/cli/src/core/opencode-adapter.server.ts` and `aider-adapter.server.ts` for adapters that were built and tuned against real, messy production data (including a real bug found and fixed that way: non-ASCII text handling in relationship detection). +- Paseo's plugin API has already broken backward compatibility once (v0.8's client/server/shared split — + see `paseo-plugin.json`'s `requirements.paseo` field and `paseo-plugin.d.ts`'s ambient module + declarations). If it happens again, verify the actual migration against a real Paseo daemon at the new + version — `paseo plugin install`/`ls` reporting `running` is the only real evidence a fix worked, not + just a clean typecheck. ## Reporting issues diff --git a/main.client.tsx b/client/sessions.tsx similarity index 99% rename from main.client.tsx rename to client/sessions.tsx index 40835cf..0b7c884 100644 --- a/main.client.tsx +++ b/client/sessions.tsx @@ -1,5 +1,5 @@ -import type { PluginSurfaceProps } from "@getpaseo/plugin"; -import { useRpc } from "@getpaseo/plugin"; +import type { PluginSurfaceProps } from "@getpaseo/plugin/client"; +import { useRpc } from "@getpaseo/plugin/client"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FlatList, Image, Modal, Pressable, ScrollView, Text, TextInput, View } from "react-native"; import { @@ -12,7 +12,7 @@ import { SessionRelationshipSchema, SessionSchema, showSessionRpc, -} from "./src/server/session-contracts.shared"; +} from "../shared/session-contracts"; import type { z } from "zod"; type SessionDto = z.infer; diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 4b3a071..e71d528 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -103,12 +103,25 @@ directory. It never touches the daemon's `pluginsEnabled` switch itself — plug code, so if plugins aren't enabled on your daemon, `wire-paseo` stops and tells you to enable them yourself first (Settings → Plugins → Enable plugins in the Paseo app). -The manual way — needed if you're developing the plugin itself, since it points `paseo` straight at your -working tree instead of a downloaded release snapshot: +The manual way — needed if you're developing the plugin itself. **Point `paseo` at `build/paseo-plugin`, not +at the repo root** — Paseo 0.8's build step scans the *entire* given directory for stray plugin modules, and +rejects anything outside `client/`/`server/`/`shared/`, including unrelated files from this monorepo's +sibling `packages/cli` workspace (verified: installing straight from the repo root fails with `Plugin +modules belong in client/, server/, or shared/: .../packages/cli/dist/index.d.ts`). `npm run package:plugin` +stages exactly the plugin's own files with no such sibling-package contamination — the same staged +directory the tarball attached to releases is built from — so point `paseo` there instead: ```bash -paseo plugin install /path/to/sessionforge -paseo plugin reload sessionforge # after any source change — never auto-reloaded +npm run package:plugin # stages the plugin (and everything it needs) into build/paseo-plugin +paseo plugin install ./build/paseo-plugin --id sessionforge # first time +``` + +After any source change, repackage then reload (reload just re-reads from the same installed path, so it +won't pick up new edits until you repackage first): + +```bash +npm run package:plugin +paseo plugin reload sessionforge paseo plugin logs sessionforge # tail console.log/console.error from the server contribution ``` diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 77c0ed4..0685aec 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -48,20 +48,29 @@ packages/cli/ @aadaa88/sessionforge — the standalone pack dist/ compiled output (git-ignored) — the actual import target for library consumers, built via `npm run build`; the CLI itself runs off raw TS via tsx, no build needed -src/server/ Paseo plugin RPC layer — depends on sessionforge like any npm package - session-contracts.shared.ts Zod RPC contracts (session.list, .show, .search, .cleanup, .archive, .restore, .delete, .discover) - session-handlers.server.ts handler implementations + background rescan scheduling (ADAPTERS = Claude Code, Codex, Gemini CLI, OpenCode, Aider) - -index.ts Paseo plugin entry point — pure plugin.handle()/addSurface()/addSidebarItem() registration only - (kept minimal: Paseo's Electron-main "evaluate" introspection pass calls this file's - top-level code without a real server context, so it must not directly call anything - imported from a .server.ts file) -main.client.tsx session browser UI (sidebar panel): search/filter/agent tabs, List/Timeline view toggle, - checkboxes + bulk actions, per-provider logo icons, click-to-preview dialog with related - sessions, per-session and per-provider file size +shared/session-contracts.ts Zod RPC contracts (session.list, .show, .search, .cleanup, .archive, .restore, .delete, .discover) — + imported by both index.client.tsx and index.server.ts, per Paseo 0.8's client/server/shared split +server/session-handlers.ts handler implementations + background rescan scheduling (ADAPTERS = Claude Code, Codex, Gemini CLI, OpenCode, Aider) +client/sessions.tsx session browser UI (sidebar panel): search/filter/agent tabs, List/Timeline view toggle, + checkboxes + bulk actions, per-provider logo icons, click-to-preview dialog with related + sessions, per-session and per-provider file size + +index.client.tsx Paseo plugin app-bundle entry point — addSurface()/addSidebarItem()/addCommandCenterItem() + registration only +index.server.ts Paseo plugin daemon-bundle entry point — plugin.handle() registration + the + background-rescan cleanup hook +paseo-plugin.d.ts hand-written ambient types for @getpaseo/plugin's root/client/server modules — Paseo + supplies the real runtime implementations at install time, so there's no real npm + package installed here to pull types from scripts/package-plugin.mjs packages the Paseo plugin (this repo's root — see above) into a self-contained - directory, then tars it — what `sessionforge wire-paseo` downloads and installs + directory, then tars it — what `sessionforge wire-paseo` downloads and installs. + Also required for *local* plugin development under Paseo 0.8+: pointing + `paseo plugin install` straight at this repo's root fails, since Paseo's build step + scans the whole given directory and rejects the sibling packages/cli workspace's own + files as "not in client/, server/, or shared/" — see MANUAL.md's Installing the Paseo + plugin section for the actual dev-loop commands (`npm run package:plugin` + install/ + reload against `build/paseo-plugin`, not the repo root) .github/workflows/ CI (typecheck/test/build on Linux/macOS/Windows) + a tag-triggered release workflow (git tag vX.Y.Z && git push origin vX.Y.Z — the tag is the version, @@ -78,10 +87,10 @@ was always necessary. Splitting that CLI (plus the whole engine underneath it) i `packages/cli` / `@aadaa88/sessionforge`, takes that one step further: nothing in it imports `@getpaseo/*`, `react`, or `react-native`, so it's usable — as a standalone binary, or as a library — by anyone who never touches Paseo, with zero risk of Paseo/React dependencies leaking into it. `packages/cli` is -`"private": true` and never published to npm; the repo root — `index.ts`, `main.client.tsx`, -`src/server/*` — is just the Paseo plugin, depending on `@aadaa88/sessionforge` as a workspace-local -dependency (npm workspace resolution symlinks it in, same mechanism a published package would use, just -without ever actually publishing). +`"private": true` and never published to npm; the repo root — `index.client.tsx`, `index.server.ts`, +`client/`, `server/`, `shared/` — is just the Paseo plugin, depending on `@aadaa88/sessionforge` as a +workspace-local dependency (npm workspace resolution symlinks it in, same mechanism a published package +would use, just without ever actually publishing). `packages/cli` builds a real `dist/` (compiled JS + `.d.ts`) as its library entry point rather than shipping raw TypeScript for that path, specifically so the plugin's cross-package import (`import ... from diff --git a/index.client.tsx b/index.client.tsx new file mode 100644 index 0000000..aff440c --- /dev/null +++ b/index.client.tsx @@ -0,0 +1,18 @@ +import type { PluginClientContext } from "@getpaseo/plugin/client"; +import { SessionsSurface } from "./client/sessions"; + +export default function contribute(client: PluginClientContext) { + client.addSurface("sessions", SessionsSurface); + client.addSidebarItem({ id: "sessionforge", title: "SessionForge", icon: "Blocks", surface: "sessions" }); + client.addCommandCenterItem({ + id: "sessionforge-open", + title: "Open SessionForge", + icon: "Blocks", + context: "global", + onSelect({ openSurface }) { + openSurface("sessions"); + }, + }); + + return () => {}; +} diff --git a/index.server.ts b/index.server.ts new file mode 100644 index 0000000..42bbb2b --- /dev/null +++ b/index.server.ts @@ -0,0 +1,45 @@ +import type { PluginServerContext } from "@getpaseo/plugin/server"; +import { + archiveSessionRpc, + cleanupSessionsRpc, + deleteSessionsRpc, + discoverSessionsRpc, + listSessionsRpc, + restoreSessionRpc, + searchSessionsRpc, + showSessionRpc, +} from "./shared/session-contracts"; +import { + archiveSessionHandler, + cleanupSessions, + deleteSessionsHandler, + discoverSessions, + listSessions, + restoreSessionHandler, + searchSessions, + showSession, + stopBackgroundDiscovery, +} from "./server/session-handlers"; + +export default function contribute(server: PluginServerContext) { + server.handle(listSessionsRpc, listSessions); + server.handle(showSessionRpc, showSession); + server.handle(searchSessionsRpc, searchSessions); + server.handle(discoverSessionsRpc, discoverSessions); + server.handle(cleanupSessionsRpc, cleanupSessions); + server.handle(archiveSessionRpc, archiveSessionHandler); + server.handle(restoreSessionRpc, restoreSessionHandler); + server.handle(deleteSessionsRpc, deleteSessionsHandler); + + return () => { + try { + stopBackgroundDiscovery(); + } catch { + // Left over from the pre-0.8 single-entry-point architecture, where Paseo's main-process + // introspection pass could invoke this same cleanup in a context without real server bindings. Now + // that server code only ever runs in the daemon subprocess bundle, that specific scenario likely + // can't happen anymore — kept as a zero-cost safety net rather than removed on an assumption that + // hasn't been directly verified against the new architecture. + } + }; +} diff --git a/index.ts b/index.ts deleted file mode 100644 index 52b8305..0000000 --- a/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { PluginContext } from "@getpaseo/plugin"; -import { SessionsSurface } from "./main.client"; -import { - archiveSessionRpc, - cleanupSessionsRpc, - deleteSessionsRpc, - discoverSessionsRpc, - listSessionsRpc, - restoreSessionRpc, - searchSessionsRpc, - showSessionRpc, -} from "./src/server/session-contracts.shared"; -import { - archiveSessionHandler, - cleanupSessions, - deleteSessionsHandler, - discoverSessions, - listSessions, - restoreSessionHandler, - searchSessions, - showSession, - stopBackgroundDiscovery, -} from "./src/server/session-handlers.server"; - -export default function contribute(plugin: PluginContext) { - plugin.handle(listSessionsRpc, listSessions); - plugin.handle(showSessionRpc, showSession); - plugin.handle(searchSessionsRpc, searchSessions); - plugin.handle(discoverSessionsRpc, discoverSessions); - plugin.handle(cleanupSessionsRpc, cleanupSessions); - plugin.handle(archiveSessionRpc, archiveSessionHandler); - plugin.handle(restoreSessionRpc, restoreSessionHandler); - plugin.handle(deleteSessionsRpc, deleteSessionsHandler); - - plugin.addSurface("sessions", SessionsSurface); - plugin.addSidebarItem({ id: "sessionforge", title: "SessionForge", icon: "Blocks", surface: "sessions" }); - plugin.addCommandCenterItem({ - id: "sessionforge-open", - title: "Open SessionForge", - icon: "Blocks", - context: "global", - onSelect({ openSurface }) { - openSurface("sessions"); - }, - }); - - return () => { - try { - stopBackgroundDiscovery(); - } catch { - // The plugin-introspection pass in Paseo's main process also invokes this cleanup, - // in a context where server-only bindings aren't callable — safe to ignore there. - } - }; -} diff --git a/packages/cli/src/core/discover.server.test.ts b/packages/cli/src/core/discover.server.test.ts index 1cc7f40..c8299ed 100644 --- a/packages/cli/src/core/discover.server.test.ts +++ b/packages/cli/src/core/discover.server.test.ts @@ -11,6 +11,14 @@ function jsonl(...lines: Record[]): string { return `${lines.map((line) => JSON.stringify(line)).join("\n")}\n`; } +/** Relative to whenever the test actually runs, not a fixed calendar date — classifySession's + * ARCHIVE_AGE_DAYS (14) threshold is age-based, so a hardcoded past timestamp is a time bomb: it read as + * "recent" when first written, then silently flipped this fixture's own classification from KEEP to + * ARCHIVE once enough real time passed, breaking this test and the two others that depend on it. */ +function daysAgoIso(days: number): string { + return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); +} + describe("discover + lifecycle actions (integration)", () => { let root: string; let previousConfigDir: string | undefined; @@ -30,14 +38,14 @@ describe("discover + lifecycle actions (integration)", () => { { type: "user", message: { content: "please implement the search command end to end" }, - timestamp: "2026-08-20T10:00:00.000Z", + timestamp: daysAgoIso(2), cwd: "/home/amgln/demo", sessionId: "keep-session", }, { type: "assistant", message: { content: [{ type: "text", text: "Sure, starting now." }] }, - timestamp: "2026-08-20T10:00:05.000Z", + timestamp: daysAgoIso(2), cwd: "/home/amgln/demo", sessionId: "keep-session", }, @@ -50,7 +58,7 @@ describe("discover + lifecycle actions (integration)", () => { jsonl({ type: "user", message: { content: "test" }, - timestamp: "2026-08-21T10:00:00.000Z", + timestamp: daysAgoIso(1), cwd: "/home/amgln/demo", sessionId: "junk-session", }), diff --git a/paseo-plugin.d.ts b/paseo-plugin.d.ts index ca5f499..768a6fd 100644 --- a/paseo-plugin.d.ts +++ b/paseo-plugin.d.ts @@ -1,11 +1,12 @@ -declare module "@getpaseo/plugin/server" { - import type { PaseoApi } from "@getpaseo/client"; - import type { ZodType, input as ZodInput, output as ZodOutput } from "zod"; +// Hand-written ambient type declarations for @getpaseo/plugin's v0.8+ split client/server/shared module +// architecture (Paseo supplies the real runtime implementations at install time; consumers don't install +// this package themselves, so there's no real npm package here to pull types from). Reorganized from the +// pre-0.8 single-module shape when this plugin migrated for the v0.8 requirements.paseo breaking change — +// see https://paseo.sh/docs/plugins/v0.8/migration. +declare module "@getpaseo/plugin" { + import type { ZodType } from "zod"; - export interface PluginRpcContract< - InputSchema extends ZodType = ZodType, - OutputSchema extends ZodType = ZodType, - > { + export interface PluginRpcContract { name: string; input: InputSchema; output: OutputSchema; @@ -34,45 +35,47 @@ declare module "@getpaseo/plugin/server" { search: PluginRpcContract; } - export interface PluginHandlerContext { - paseo: PaseoApi; - } - export function defineRpc(definition: { name: string; input: InputSchema; output: OutputSchema; }): PluginRpcContract; - export function defineAttachmentSource( - definition: Definition, - ): Definition; + export function defineAttachmentSource(definition: Definition): Definition; export const PluginAttachmentItemSchema: import("zod").ZodType; export const PluginAttachmentSearchPayloadSchema: import("zod").ZodType; + + export type PluginCleanup = () => void | Promise; } -declare module "@getpaseo/plugin" { +declare module "@getpaseo/plugin/server" { + import type { PaseoApi } from "@getpaseo/client"; + import type { input as ZodInput, output as ZodOutput, ZodType } from "zod"; + import type { PluginCleanup, PluginRpcContract } from "@getpaseo/plugin"; + + export interface PluginHandlerContext { + paseo: PaseoApi; + } + + export interface PluginServerContext { + handle( + contract: PluginRpcContract, + handler: ( + input: ZodOutput, + context: PluginHandlerContext, + ) => ZodInput | Promise>, + ): void; + } + + export type PluginServerContribution = (server: PluginServerContext) => PluginCleanup; +} + +declare module "@getpaseo/plugin/client" { import type { ComponentType } from "react"; import type { PaseoApi } from "@getpaseo/client"; - import type { ZodType, input as ZodInput, output as ZodOutput } from "zod"; - import type { - PluginAttachmentSourceContribution, - PluginHandlerContext, - PluginRpcContract, - } from "@getpaseo/plugin/server"; - - export { - PluginAttachmentItemSchema, - PluginAttachmentSearchPayloadSchema, - defineAttachmentSource, - defineRpc, - type PluginAttachmentItem, - type PluginAttachmentSearchPayload, - type PluginAttachmentSourceContribution, - type PluginHandlerContext, - type PluginRpcContract, - } from "@getpaseo/plugin/server"; + import type { input as ZodInput, output as ZodOutput, ZodType } from "zod"; + import type { PluginAttachmentSourceContribution, PluginCleanup, PluginRpcContract } from "@getpaseo/plugin"; export interface PluginTheme { readonly colors: { @@ -140,11 +143,27 @@ declare module "@getpaseo/plugin" { } export type PluginPanelLocation = "workspace" | "explorer"; - export interface PluginOpenPanelOptions { location?: PluginPanelLocation; } + export interface PluginOpenPanelOptions { + location?: PluginPanelLocation; + } export type PluginWorkspacePanelContribution = - | { id: string; title: string; icon: string; locations?: readonly PluginPanelLocation[]; context: "workspace"; Component: ComponentType } - | { id: string; title: string; icon: string; locations?: readonly PluginPanelLocation[]; context: "agent"; Component: ComponentType }; + | { + id: string; + title: string; + icon: string; + locations?: readonly PluginPanelLocation[]; + context: "workspace"; + Component: ComponentType; + } + | { + id: string; + title: string; + icon: string; + locations?: readonly PluginPanelLocation[]; + context: "agent"; + Component: ComponentType; + }; export interface PluginSidebarContribution { id: string; @@ -203,18 +222,32 @@ declare module "@getpaseo/plugin" { } export type PluginCommandCenterItemContribution = - | { id: string; title: string; icon: string; keywords?: readonly string[]; context: "global"; onSelect(context: PluginGlobalCommandContext): void | Promise } - | { id: string; title: string; icon: string; keywords?: readonly string[]; context: "workspace"; onSelect(context: PluginWorkspaceCommandContext): void | Promise } - | { id: string; title: string; icon: string; keywords?: readonly string[]; context: "agent"; onSelect(context: PluginAgentCommandContext): void | Promise }; - - export interface PluginContext { - handle( - contract: PluginRpcContract, - handler: ( - input: ZodOutput, - context: PluginHandlerContext, - ) => ZodInput | Promise>, - ): void; + | { + id: string; + title: string; + icon: string; + keywords?: readonly string[]; + context: "global"; + onSelect(context: PluginGlobalCommandContext): void | Promise; + } + | { + id: string; + title: string; + icon: string; + keywords?: readonly string[]; + context: "workspace"; + onSelect(context: PluginWorkspaceCommandContext): void | Promise; + } + | { + id: string; + title: string; + icon: string; + keywords?: readonly string[]; + context: "agent"; + onSelect(context: PluginAgentCommandContext): void | Promise; + }; + + export interface PluginClientContext { addSurface(id: string, Component: ComponentType): void; addSidebarItem(contribution: PluginSidebarContribution): void; addWorkspacePanel(contribution: PluginWorkspacePanelContribution): void; @@ -223,8 +256,7 @@ declare module "@getpaseo/plugin" { addTheme(contribution: PluginThemeContribution): void; } - export type PluginCleanup = () => void | Promise; - export type PluginContribution = (plugin: PluginContext) => PluginCleanup; + export type PluginClientContribution = (client: PluginClientContext) => PluginCleanup; export function useRpc( contract: PluginRpcContract, @@ -232,13 +264,7 @@ declare module "@getpaseo/plugin" { export function usePaseo(): PaseoApi; - export function useWorkspace( - workspaceId: string, - selector: (workspace: PluginWorkspaceSnapshot) => Selection, - ): Selection | null; + export function useWorkspace(workspaceId: string, selector: (workspace: PluginWorkspaceSnapshot) => Selection): Selection | null; - export function useAgent( - agentId: string, - selector: (agent: PluginAgentSnapshot) => Selection, - ): Selection | null; + export function useAgent(agentId: string, selector: (agent: PluginAgentSnapshot) => Selection): Selection | null; } diff --git a/paseo-plugin.json b/paseo-plugin.json index b8b0d2b..1027ab5 100644 --- a/paseo-plugin.json +++ b/paseo-plugin.json @@ -1,3 +1,4 @@ { - "id": "sessionforge" + "id": "sessionforge", + "requirements": { "paseo": ">=0.8.0" } } diff --git a/scripts/package-plugin.mjs b/scripts/package-plugin.mjs index 263f4de..703c4e6 100644 --- a/scripts/package-plugin.mjs +++ b/scripts/package-plugin.mjs @@ -1,7 +1,10 @@ #!/usr/bin/env node -// Packages this repo's Paseo plugin (index.ts, main.client.tsx, src/server/*) into a self-contained -// directory, then tars it — the artifact `sessionforge wire-paseo` downloads and points a local -// `paseo plugin install` at, so a user doesn't need to clone this whole monorepo just to get the plugin. +// Packages this repo's Paseo plugin (index.client.tsx, index.server.ts, client/, server/, shared/) into a +// self-contained directory, then tars it — the artifact `sessionforge wire-paseo` downloads and points a +// local `paseo plugin install` at, so a user doesn't need to clone this whole monorepo just to get the +// plugin. File layout follows Paseo's v0.8+ split client/server/shared architecture (see +// https://paseo.sh/docs/plugins/v0.8/migration) — "the directory is the boundary": code under client/ +// compiles only into the app bundle, server/ only into the daemon bundle, shared/ into both. // // This can't just be "git clone the repo and let Paseo build it" (verified empirically): Paseo's // git-source plugin install does a fresh checkout and bundles the plugin directly, with no npm-workspace @@ -24,10 +27,20 @@ const buildDir = join(repoRoot, "build"); const stageDir = join(buildDir, "paseo-plugin"); const archivePath = join(buildDir, "sessionforge-paseo-plugin.tar.gz"); -// Everything the plugin's own source actually imports (`index.ts`, `main.client.tsx`, `src/server/*`) — -// verified by reading the actual import statements, not guessed. `tsconfig.json` isn't required at -// runtime (Paseo transpiles the plugin's TS/TSX on the fly) but costs nothing to include. -const PLUGIN_FILES = ["paseo-plugin.json", "paseo-plugin.d.ts", "index.ts", "main.client.tsx", "package.json", "tsconfig.json", "src/server"]; +// Everything the plugin's own source actually imports (`index.client.tsx`, `index.server.ts`, `client/`, +// `server/`, `shared/`) — verified by reading the actual import statements, not guessed. `tsconfig.json` +// isn't required at runtime (Paseo transpiles the plugin's TS/TSX on the fly) but costs nothing to include. +const PLUGIN_FILES = [ + "paseo-plugin.json", + "paseo-plugin.d.ts", + "index.client.tsx", + "index.server.ts", + "client", + "server", + "shared", + "package.json", + "tsconfig.json", +]; function main() { const cliDist = join(repoRoot, "packages", "cli", "dist"); diff --git a/src/server/session-handlers.server.ts b/server/session-handlers.ts similarity index 100% rename from src/server/session-handlers.server.ts rename to server/session-handlers.ts diff --git a/src/server/session-contracts.shared.ts b/shared/session-contracts.ts similarity index 98% rename from src/server/session-contracts.shared.ts rename to shared/session-contracts.ts index c694816..5210bf0 100644 --- a/src/server/session-contracts.shared.ts +++ b/shared/session-contracts.ts @@ -1,4 +1,4 @@ -import { defineRpc } from "@getpaseo/plugin/server"; +import { defineRpc } from "@getpaseo/plugin"; import { z } from "zod"; export const AgentIdSchema = z.enum(["claude-code", "codex", "gemini-cli", "aider", "opencode", "custom"]);