Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions main.client.tsx → client/sessions.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<typeof SessionSchema>;
Expand Down
21 changes: 17 additions & 4 deletions docs/MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the README's stale manual install command

This migration makes paseo plugin install /path/to/sessionforge fail for a repository checkout, as this new section explains, but README.md:54-56 and README.ko.md:54-56 still direct plugin developers to use exactly that command. Users following the project landing page will therefore hit the v0.8 module-location rejection instead of staging the plugin first; update those links/text to use npm run package:plugin and build/paseo-plugin.

Useful? React with 👍 / 👎.

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
```

Expand Down
41 changes: 25 additions & 16 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
18 changes: 18 additions & 0 deletions index.client.tsx
Original file line number Diff line number Diff line change
@@ -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 () => {};
}
45 changes: 45 additions & 0 deletions index.server.ts
Original file line number Diff line number Diff line change
@@ -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.
}
};
}
55 changes: 0 additions & 55 deletions index.ts

This file was deleted.

14 changes: 11 additions & 3 deletions packages/cli/src/core/discover.server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ function jsonl(...lines: Record<string, unknown>[]): 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;
Expand All @@ -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",
},
Expand All @@ -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",
}),
Expand Down
Loading
Loading