diff --git a/AGENTS.md b/AGENTS.md index 8403f19..741c4d2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Paseo Productivity Plugins — AGENTS.md -This repository contains trusted local Paseo plugins for workspace notes and review workflows, reusable prompts, Linear issue work, and development-port management. Each plugin is independently installable on a local or remote Paseo daemon, and the repository is maintained for source distribution to other Paseo users. +This repository contains trusted local Paseo plugins for workspace notes and board workflows, Linear issue work, and development-port management. Each plugin is independently installable on a local or remote Paseo daemon, and the repository is maintained for source distribution to other Paseo users. ## Non-Negotiable Core Principles @@ -19,8 +19,7 @@ User instructions take precedence. Preserve unrelated files and running processe ## Project Glossary -- **Workspace Companion**: Notes, one unified workspace board, and transcript-aware manual QA plans. -- **Prompt Library**: Globally stored reusable prompts exposed in the sidebar and composer attachment picker. +- **Workspace Companion**: Workspace-tab notes and one unified workspace board. - **Linear**: Direct GraphQL integration for reading and explicitly confirmed issue mutations. - **Dev Ports**: Same-user workspace process discovery, safe termination, and Tailscale Serve controls. - **Board state**: `running` and `error` are derived from Paseo activity; `unreviewed`, `recheck`, and `approved` are plugin-owned review markers. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 566befb..73fd5aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ For UI changes, test a wide desktop view and a compact view. Use Paseo theme col - Keep Node APIs, credentials, shell commands, Git access, and filesystem access in `*.server.ts` files. - Validate RPC inputs and outputs with Zod. - Use Paseo's SDK for normal workspace and agent operations. -- Never log credentials, transcript contents, or private prompt-library entries. +- Never log credentials, transcript contents, or private workspace notes. Paseo plugin changes require a source reload: diff --git a/README.md b/README.md index e5a2f2b..df40176 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ # Paseo plugins -Four local plugins for [Paseo](https://paseo.sh): workspace notes and QA planning, reusable prompts, Linear issue work, and development-port controls. +Three local plugins for [Paseo](https://paseo.sh): workspace notes and an agent board, Linear issue work, and development-port controls. > [!WARNING] > Paseo plugins are experimental, trusted code. Server-side plugin code runs without a sandbox on the daemon machine. Read the source before installing it, and expect Paseo API changes to require updates. ## Included plugins -| Plugin | What it adds | Extra requirement | -| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | -| [Workspace Companion](plugins/workspace-companion) | Markdown notes, a draggable agent board with workspace archiving, and transcript-aware manual QA plans | A configured Paseo coding-agent provider for QA generation | -| [Prompt Library](plugins/prompt-library) | A reusable prompt library in the sidebar and composer attachment picker | None | -| [Linear](plugins/linear) | Issue search, comments, status changes, and priority changes | A Linear personal API key | -| [Dev Ports](plugins/dev-ports) | Workspace port discovery, safe process stopping, browser links, and private Tailscale Serve controls | Linux daemon; Tailscale is optional | +| Plugin | What it adds | Extra requirement | +| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------- | +| [Workspace Companion](plugins/workspace-companion) | Markdown notes with auto-saving preview checklists, plus a draggable board and workspace archiving | None | +| [Linear](plugins/linear) | Paged issue search, Markdown issue detail, comments, assignee, status, and priority changes | A Linear personal API key | +| [Dev Ports](plugins/dev-ports) | Workspace port discovery, safe process stopping, browser links, and private Tailscale Serve controls | Linux daemon; Tailscale is optional | -Each plugin is independently installable. You do not need to enable all four. +Each plugin is independently installable. You do not need to enable all three. ## Quick start @@ -38,7 +37,6 @@ In Paseo, open **Settings → Plugins** and enable plugins. Then install only th ```bash paseo plugin install "$PWD/plugins/workspace-companion" -paseo plugin install "$PWD/plugins/prompt-library" paseo plugin install "$PWD/plugins/linear" paseo plugin install "$PWD/plugins/dev-ports" paseo plugin ls @@ -58,7 +56,6 @@ bun install --frozen-lockfile bun run test bun run typecheck paseo plugin reload workspace-companion -paseo plugin reload prompt-library paseo plugin reload linear paseo plugin reload dev-ports ``` @@ -72,7 +69,6 @@ Plugin state is stored under the daemon's Paseo home, normally `~/.paseo/plugin- - Linear mutations require an explicit confirmation in the UI. - Dev Ports only shows same-user listeners inside registered Paseo workspaces. It sends `SIGTERM` and never force-kills a process. - Dev Ports creates private Tailscale Serve mappings. It never enables Tailscale Funnel. -- Workspace Companion sends the focused agent transcript and current workspace changes to the provider configured for QA planning. Read [SECURITY.md](SECURITY.md) before installing the plugins on a shared daemon. @@ -89,7 +85,7 @@ The suite uses Bun workspaces. Each plugin keeps its own Paseo manifest, strict ## Paseo compatibility -Paseo does not currently provide a stable plugin distribution format. This repository distributes source directories that the daemon compiles locally. Use a recent Paseo release and consult the official [plugin quickstart](https://paseo.sh/docs/plugins) and [plugin reference](https://paseo.sh/docs/plugins/reference) when the API changes. +Paseo's plugin API is experimental. This repository distributes source directories that the daemon compiles locally. Use a recent Paseo release and consult the official [plugin quickstart](https://paseo.sh/docs/plugins) and [plugin reference](https://paseo.sh/docs/plugins/reference) when the API changes. ## License diff --git a/SECURITY.md b/SECURITY.md index 1f4e666..e9d52ff 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,8 +8,7 @@ Review the plugin directory before installing it. On shared daemons, treat plugi ## Plugin permissions -- `workspace-companion` reads workspace Git metadata and the focused agent's projected transcript when you manually generate a QA plan. It stores notes, board state, and QA plans under Paseo's plugin-data directory. -- `prompt-library` reads and writes its own prompt store under Paseo's plugin-data directory. Attached prompts become part of the message sent to the selected agent. +- `workspace-companion` stores notes and board state under Paseo's plugin-data directory. Refining a note sends its current contents to the selected workspace agent. - `linear` sends requests to Linear with the configured personal API key. Writes require an explicit confirmation in the panel. - `dev-ports` reads Linux socket and `/proc` metadata, can send `SIGTERM` to a verified same-user workspace process, and can create or remove Tailscale Serve mappings that it owns. @@ -17,7 +16,7 @@ None of the plugins should log credentials or stored content. ## Credentials -Never commit a Linear key or a QA-provider secret. Keep credentials in the daemon environment or the documented user-only configuration files. Restrict credential files to the daemon user: +Never commit a Linear key. Keep it in the daemon environment or the documented user-only configuration file. Restrict credential files to the daemon user: ```bash chmod 600 ~/.config/paseo-plugins/*.env diff --git a/bun.lock b/bun.lock index 998224c..eeb463b 100644 --- a/bun.lock +++ b/bun.lock @@ -39,19 +39,6 @@ "zod": "^4.4.3", }, }, - "plugins/prompt-library": { - "name": "prompt-library", - "version": "0.0.0", - "devDependencies": { - "@getpaseo/client": "^0.4.0", - "@tanstack/react-query": "^5.90.11", - "@types/react": "~19.2.0", - "react": "19.1.0", - "react-native": "0.81.5", - "typescript": "^5.9.3", - "zod": "^4.4.3", - }, - }, "plugins/workspace-companion": { "name": "workspace-companion", "version": "0.0.0", @@ -541,8 +528,6 @@ "promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="], - "prompt-library": ["prompt-library@workspace:plugins/prompt-library"], - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], diff --git a/docs/installation.md b/docs/installation.md index 37a288e..581d1c5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -31,14 +31,14 @@ bun run typecheck Install a plugin with an absolute directory path: ```bash -paseo plugin install "$PWD/plugins/prompt-library" +paseo plugin install "$PWD/plugins/workspace-companion" paseo plugin ls ``` The plugin should report `running`. If it does not, inspect its retained log: ```bash -paseo plugin logs prompt-library +paseo plugin logs workspace-companion ``` ## Install on a remote host @@ -70,24 +70,6 @@ paseo plugin reload linear The plugin reads the key when it handles a request. It does not return or log the key. -## Configure Workspace Companion QA - -QA generation starts a temporary Paseo agent. By default it uses `codex/gpt-5.6-sol`, `auto-review`, and high thinking. If that provider or model is not available on your daemon, create: - -```text -~/.config/paseo-plugins/workspace-companion.env -``` - -Set the provider and optional mode values: - -```bash -PASEO_QA_PROVIDER=codex/gpt-5.5 -PASEO_QA_MODE=auto-review -PASEO_QA_THINKING=high -``` - -Use provider, mode, and thinking IDs reported by your Paseo installation. Reload Workspace Companion after changing the file. - ## Configure Dev Ports Dev Ports requires a Linux daemon with `ss` and `/proc`. Port discovery and safe process stopping work without Tailscale. @@ -130,7 +112,6 @@ Removing a plugin removes Paseo's installation record. It does not delete the cl | Plugin failed to load | Run `bun run typecheck`, then inspect `paseo plugin logs `. | | Edited code is stale | Run `paseo plugin reload `. | | Linear says it is not configured | Put the key in the daemon environment or the documented credential file, then reload. | -| QA generation fails immediately | Configure a provider, model, mode, and thinking option that exist on the daemon. | | Dev Ports shows no listeners | Confirm the daemon is Linux, `ss` is installed, and the process directory belongs to a registered workspace. | | Tailscale access is denied | Set the daemon user as the Tailscale operator once. | diff --git a/docs/mocks/native-plugin-surfaces.html b/docs/mocks/native-plugin-surfaces.html index 5dad81a..81a7faa 100644 --- a/docs/mocks/native-plugin-surfaces.html +++ b/docs/mocks/native-plugin-surfaces.html @@ -205,6 +205,7 @@ margin-bottom: 14px; } .linear-search, + .assignee-search, .linear-comment { width: 100%; min-height: 42px; @@ -247,9 +248,10 @@ } .issue-state, .choice { - padding: 5px 9px; + min-height: 30px; + padding: 4px 8px; border: 0; - border-radius: 999px; + border-radius: 8px; color: var(--muted); background: var(--control); font-size: 12px; @@ -275,6 +277,24 @@ margin-top: 12px; color: var(--text); } + .markdown-copy { + display: grid; + gap: 8px; + margin-top: 12px; + color: var(--text); + } + .markdown-copy code { + padding: 2px 5px; + border-radius: 5px; + color: var(--accent); + background: var(--control); + font-family: ui-monospace, monospace; + font-size: 12px; + } + .markdown-copy ul { + margin: 0; + padding-left: 20px; + } .section-label { margin-top: 18px; color: var(--muted); @@ -291,6 +311,28 @@ color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--raised)); } + .pager { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 8px; + margin-top: 10px; + } + .pager .page-number { + color: var(--muted); + font-size: 12px; + font-variant-numeric: tabular-nums; + } + .pager .secondary:last-child { + justify-self: end; + } + .assignee-search { + min-height: 34px; + margin-top: 8px; + padding: 6px 9px; + border-radius: 9px; + font-size: 12px; + } .linear-comment { min-height: 86px; margin-top: 8px; @@ -373,6 +415,47 @@ Menlo, monospace; } + .note-preview { + display: grid; + align-content: start; + gap: 6px; + } + .note-preview h2 { + margin: 6px 0 2px; + font-size: 19px; + } + .task-row { + display: grid; + grid-template-columns: 40px 1fr; + align-items: center; + min-height: 40px; + } + .task-checkbox { + display: grid; + width: 40px; + height: 40px; + place-items: center; + border: 0; + color: var(--text); + background: transparent; + } + .task-checkbox::before { + display: grid; + width: 18px; + height: 18px; + place-items: center; + border: 1px solid var(--line); + border-radius: 5px; + content: ""; + } + .task-checkbox.checked::before { + border-color: var(--accent); + color: #102019; + background: var(--accent); + content: "✓"; + font-size: 12px; + font-weight: 700; + } .panel-footer { display: flex; align-items: center; @@ -605,18 +688,27 @@

Refine remote development workflow

+
+ + Page 1 + +
- +
SYS-218

Make plugin panels match Paseo

System · Unassigned

-

- Bring the Linear panel in line with Paseo's quiet grouped layout. -

+
+

Deliverable: Match Paseo's grouped layout.

+
    +
  • Keep controls compact
  • +
  • Verify the workspace tab flow
  • +
+
@@ -634,6 +726,15 @@

Make plugin panels match Paseo

+
+ + +
+ + + +
+
+
+

Decisions

+
+ + Keep the board limited to five states +
+
+ + Test the drag interaction on desktop +
+

Follow-ups

+
+ + Verify compact layout on iPhone +
-
-
-
-

QA review

-

Manual checks inferred from the work and its conversation

-
- -
-
- 18 transcript messages - 7 changed files - +184 −96 - Generated 4:32 AM -
-
-
What changed
-
-

- The workspace companion panels now use Paseo's quieter grouped - layout. -

-

- Review planning now turns the agent conversation and current - diff into manual QA flows. -

-
-
-
-
Test these flows
-
-
-
-

Generate a QA plan from an agent

- High -
-

- The review action now reads transcript context and - working-tree changes. -

-
    -
  1. Focus an agent with completed workspace changes.
  2. -
  3. Open QA review and choose Regenerate.
  4. -
  5. - Confirm the plan names the changed screens and gives - executable checks. -
  6. -
-
-
-
-

Edit and preview a workspace note

- Normal -
-

- The note editor, preview, dirty state, and save controls were - redesigned. -

-
    -
  1. Edit Markdown and verify Unsaved changes appears.
  2. -
  3. Switch between Write and Preview without losing text.
  4. -
  5. - Save, reopen the panel, and confirm the note persists. -
  6. -
-
-
-
-
-
Watch for
-
    -
  • - Plans that describe implementation details instead of - user-visible checks -
  • -
  • Missing empty and error states on compact clients
  • -
-
-
diff --git a/docs/mocks/plugin-suite.html b/docs/mocks/plugin-suite.html index 22f4f9f..45c15c7 100644 --- a/docs/mocks/plugin-suite.html +++ b/docs/mocks/plugin-suite.html @@ -232,7 +232,6 @@

Paseo

Workspaces
Schedules
Agent board
-
Prompt library
Dev ports
@@ -297,13 +296,6 @@

Notes

-
- QA plan ready -

- Test the updated authentication and migration flows in the app. -

-
-
diff --git a/docs/plans/paseo-plugin-suite.md b/docs/plans/paseo-plugin-suite.md index 2ebba0a..29bbd80 100644 --- a/docs/plans/paseo-plugin-suite.md +++ b/docs/plans/paseo-plugin-suite.md @@ -2,7 +2,7 @@ ## Goal -Add four trusted local Paseo plugins: workspace notes and review workflows, a reusable prompt library, direct Linear issue tools, and safe development-port controls. +Maintain three trusted local Paseo plugins: workspace notes and board workflows, direct Linear issue tools, and safe development-port controls. ## Context @@ -10,17 +10,16 @@ Paseo 0.5 exposes global surfaces, sidebar items, workspace/agent panels, comman ## Scope -1. Workspace-scoped Markdown notes with write and preview modes; the current agent can refine a replacement note that the user reviews before saving. +1. Workspace-scoped Markdown notes with write and preview modes. Preview task markers are interactive and save the current note immediately. The current agent can refine a replacement note that the user reviews before saving. 2. One workspace board with exactly five columns, ordered `Running`, `Unreviewed`, `Recheck`, `Error`, and `Approved`. `Running` and `Error` are live states; the other three are plugin-owned review states. A separate header drop target archives the actual Paseo workspace without introducing a sixth status. -3. A global prompt library exposed through both a sidebar surface and composer attachment picker. -4. A direct Linear GraphQL panel for search, issue detail, comments, status, and priority updates. Every write requires a second explicit confirmation. -5. A manual QA plan generated from the focused agent's transcript and an overview of current workspace changes. It identifies user-facing screens and flows to test rather than reviewing code. -6. Workspace-scoped listening-port discovery, safe `SIGTERM`, private Tailscale Serve controls, and forwarded links opened by the operating system browser. +3. A workspace-context Linear GraphQL panel with cursor-backed page navigation, Markdown-rendered issue descriptions and comments, status, priority, and assignee updates. Every write requires a second explicit confirmation. +4. Workspace-scoped listening-port discovery, safe `SIGTERM`, private Tailscale Serve controls, and forwarded links opened by the operating system browser. ## Non-goals - Replacing Paseo's native workspace lifecycle or maintaining a separate plugin-only archive. -- Automatic review popovers, automatic QA-plan generation, or persistent reviewer agents. +- QA-plan generation or code-review automation. +- Reusable prompt storage or composer prompt attachments. - Killing processes outside registered workspaces or escalating to `SIGKILL`. - Public exposure through Tailscale Funnel. - Linear project administration, bulk edits, or automation. @@ -43,9 +42,7 @@ The workspace companion renders one card per workspace. A real agent or workspac Archive remains an action outside the workflow state machine. Desktop/web users drag a card to a header target, while touch and keyboard users use a two-step card action. The client calls `paseo.workspaces.archive`, removes the workspace and its agents from the query cache immediately, and restores the previous cache if the daemon reports an error or does not confirm `archivedAt`. -QA-plan generation is asynchronous because plugin RPC requests have a shorter timeout than a high-reasoning analyst run. The command records a generating state immediately, the panel polls that state, and a temporary auto-archived Sol agent analyzes the focused agent's public projected timeline together with the current Git overview. The analyst receives a constrained JSON schema and explicit product-QA instructions: no edits, no code-quality findings, and no transcript instructions treated as commands. - -Global plugin surfaces do not receive Paseo's internal router or external-link helper. Workspace cards therefore use Paseo's canonical host/workspace URL on web and its `paseo://` deep link on native. Dev Ports calls the desktop preload's allowlisted `opener.openUrl` bridge when present, then falls back to a normal browser tab or native Linking. +Global plugin surfaces do not receive Paseo's internal router or external-link helper. Workspace cards therefore use Paseo's canonical host/workspace URL on web and its `paseo://` deep link on native. Dev Ports and Linear call the desktop preload's allowlisted `opener.openUrl` bridge when present, then fall back to a normal browser tab or native Linking. ## Alternatives considered @@ -58,17 +55,15 @@ Global plugin surfaces do not receive Paseo's internal router or external-link h ## Implementation phases -1. Scaffold the repository and four plugin packages; establish contracts and pure domain tests. -2. Build Workspace Companion notes, board, review states, and on-demand transcript-aware QA plans. -3. Build Prompt Library storage, management surface, and attachment search. -4. Build Linear GraphQL adapter and confirmed mutations. -5. Build Dev Ports discovery, safety classifier, process controls, and Tailscale adapter. -6. Typecheck, test, install, reload, inspect daemon health/logs, and review the complete diff. +1. Maintain Workspace Companion notes, board, review states, and workspace archiving. +2. Maintain the Linear GraphQL adapter and confirmed mutations. +3. Maintain Dev Ports discovery, safety classification, process controls, and Tailscale adapter. +4. Typecheck, test, install, reload, inspect daemon health/logs, and review the complete diff. ## Validation -- Focused Bun tests for concurrent persistence, board-state precedence, review workflow transitions and legacy-state migration, workspace routes, QA evidence parsing and plan construction, Linear credential/confirmation/error behavior, external browser selection, port parsing/safety, Tailscale command construction, and mapping ownership. No test mutates a live Linear account or Tailscale configuration. -- Strict TypeScript checks in all four packages. +- Focused Bun tests for concurrent persistence, Markdown task toggling, board-state precedence, review workflow transitions and legacy-state migration, workspace routes, Linear credential/confirmation/error behavior, external browser selection, port parsing/safety, Tailscale command construction, and mapping ownership. No test mutates a live Linear account or Tailscale configuration. +- Strict TypeScript checks in all three packages. - Paseo plugin installation/reload without daemon restart. - Daemon status and plugin list/log inspection after installation. @@ -81,4 +76,4 @@ Global plugin surfaces do not receive Paseo's internal router or external-link h ## Status -Implemented and locally installed on 2026-08-21. The Agent Board and Dev Ports native-quality pass was completed on 2026-08-22 against Paseo's current public plugin contract and repository design guidance. On 2026-08-22, Agent Board ordering was made persistent and movement optimistic, the Linear panel was rebuilt around Paseo's grouped rows and restrained hierarchy, and Workspace Companion's Notes and QA review panels received the same native-quality pass. QA review now generates on demand from the focused agent's transcript and current changes. The Agent Board now archives real Paseo workspaces through a header drop target with optimistic rollback and an accessible two-step fallback. Public installation guides and portable Linear and QA configuration were added before distribution. All 42 focused tests, all four strict typechecks, and formatting pass. Workspace Companion and Linear reload as `running`, and both retained logs end in `Plugin ready` without errors. +Implemented and locally installed on 2026-08-21. The Agent Board and Dev Ports native-quality pass was completed on 2026-08-22. Agent Board ordering is persistent and optimistic, and archiving uses a header drop target with rollback plus an accessible two-step fallback. On 2026-08-28, Prompt Library and QA Review were removed, while Workspace Notes and Linear moved to workspace-context panels so Paseo lists them in its native workspace tab menu. Linear now uses bottom Previous/Next navigation, compact field controls, confirmed assignee changes, Markdown descriptions and comments, and operating-system browser links. Notes preview checklists update the Markdown and save immediately. The maintained suite passes 39 focused tests, all three strict typechecks, and formatting; Workspace Companion and Linear reload as `running` with clean `Plugin ready` logs. diff --git a/docs/plans/public-release.md b/docs/plans/public-release.md index 997b955..5646656 100644 --- a/docs/plans/public-release.md +++ b/docs/plans/public-release.md @@ -2,7 +2,7 @@ ## Goal -Publish the existing four-plugin suite to `Dey11/paseo-plugins` with enough setup, security, and troubleshooting information for another Paseo user to install one plugin without maintainer help. +Publish the plugin suite to `Dey11/paseo-plugins` with enough setup, security, and troubleshooting information for another Paseo user to install one plugin without maintainer help. ## Context @@ -12,7 +12,7 @@ Paseo's plugin API is experimental and currently distributes local source direct - Root installation, update, removal, security, and contribution guides - One README per plugin -- Portable configuration files for Linear and Workspace Companion QA +- Portable configuration for Linear - CI for formatting, tests, and strict TypeScript checks - MIT license and repository metadata @@ -20,7 +20,7 @@ Paseo's plugin API is experimental and currently distributes local source direct - npm publication or a plugin marketplace package - Automatic installation scripts that enable trusted code without review -- Changing the product behavior of the four plugins +- Changing plugin product behavior beyond the release requirements - Making the GitHub repository public; the owner will do that later ## Chosen approach @@ -38,8 +38,7 @@ Use the existing Bun workspace as the repository root. Paseo installs each `plug - Paseo API changes may break source compatibility. The guides link to current official plugin documentation and describe the API as experimental. - Tailscale and Linear operate outside Paseo. Their guides state exact permissions and avoid live mutation tests. -- QA generation requires provider-specific IDs. The plugin exposes those IDs through daemon environment variables or a user-only configuration file. ## Status -Completed on 2026-08-22. The committed tree passes formatting, 39 focused tests, and all four strict TypeScript checks from a fresh clone. The release is published to the private `Dey11/paseo-plugins` repository with a clean `main` history; the owner will make it public later. +Completed on 2026-08-22. The initial four-plugin release was published with a clean `main` history. Prompt Library and QA Review were removed from the maintained suite on 2026-08-28; the root README and current installation guide define the supported set. diff --git a/docs/usage.md b/docs/usage.md index af3a4d9..f01885c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -4,21 +4,18 @@ The plugins are independently installable against a Paseo daemon. If a client wa ## Workspace Companion -- Open **Notes** from an agent's right panel or the command center. Edit Markdown in **Write**, inspect it in **Preview**, and save it to the daemon. -- **Refine with agent** sends the current note to the agent whose panel is open. The returned Markdown stays as an unsaved draft until you review and save it. +- Open **Workspace notes** from the workspace **+** menu or the Command Center. Edit Markdown in **Write**, inspect it in **Preview**, and save it to the daemon. +- Markdown task lines such as `- [ ] Test checkout` become interactive checkboxes in **Preview**. Selecting one updates the Markdown and immediately saves the entire current note, including any other unsaved edits. +- **Refine with agent** sends the current note to the most recently updated agent in that workspace. The returned Markdown stays as an unsaved draft until you review and save it. - Open **Agent board** from the sidebar. There is one workspace board with **Running**, **Unreviewed**, **Recheck**, **Error**, and **Approved** columns. Drag cards to reorder any column on desktop/web; the order persists across reloads. Finished workspaces can also move among the three review columns, while Running and Error stay tied to live Paseo activity. Placements appear immediately while the daemon saves them. Use **Move to** for a non-drag review-state change, and select the card body to open its workspace. - To archive a workspace on desktop/web, drag its card onto **Archive** in the board header. The board removes it immediately and restores it if Paseo rejects the request. Touch and keyboard users can press **Archive**, then **Confirm archive**, on the card. This archives the actual Paseo workspace and closes its runtime; Archive is not another board status. -- Open **QA review** from an agent panel. **Generate QA plan** reads that agent's conversation and the current workspace changes, then starts a temporary Sol analyst to identify the screens and flows worth manually testing. The panel reports product behavior to exercise, concrete steps, and things to watch for; it does not perform a code review. The temporary analyst archives itself when finished. - -## Prompt Library - -- Open **Prompt library** from the sidebar to create, edit, tag, search, or delete prompts. -- In any composer, open the attachment picker and choose **Prompt library**. Selecting an item attaches a snapshot of its prompt text to that message. ## Linear -- Open **Linear** from an agent's right panel or command center. Search by issue text, page through all matching issues, open an issue, review comments, and open the canonical Linear URL. -- Choosing a status, priority, or comment creates a confirmation card. Nothing is written to Linear until **Confirm update** is pressed. +- Open **Linear** from the workspace **+** menu or Command Center. Search by issue text, use **Previous** and **Next** at the bottom to page through matching issues, open an issue, and review its comments. +- **Open in Linear** sends that issue's canonical URL to the operating system browser instead of Paseo's embedded browser pane. +- Issue descriptions and recent comments render Markdown headings, emphasis, links, lists, task markers, blockquotes, and inline or fenced code. Markdown links also open in the operating system browser. +- Choosing a status, priority, assignee, or comment creates a confirmation card. Nothing is written to Linear until **Confirm update** is pressed. The assignee picker contains active, assignable members of the issue's team. ## Dev Ports diff --git a/plugins/linear/README.md b/plugins/linear/README.md index 6a645d4..36be118 100644 --- a/plugins/linear/README.md +++ b/plugins/linear/README.md @@ -1,14 +1,16 @@ # Linear -The Linear plugin adds an agent-context panel for: +The Linear plugin adds a workspace panel for: -- Searching issues by text -- Reading issue descriptions and comments -- Opening the canonical issue in a browser -- Changing status or priority +- Searching issues by text with Previous/Next pagination +- Reading Markdown-formatted issue descriptions and comments +- Opening the canonical issue in the operating system browser +- Changing status, priority, or assignee - Adding comments -Every write creates a confirmation card. Linear receives nothing until you press **Confirm update**. +Every write creates a confirmation card. Linear receives nothing until you press **Confirm update**. The assignee picker lists active, assignable members of the issue's team and supports returning the issue to **Unassigned**. + +Descriptions and recent comments render headings, bold and italic text, links, lists, task markers, blockquotes, inline code, and fenced code blocks. Links use the operating system browser. ## Install @@ -21,7 +23,7 @@ paseo plugin install "$PWD/plugins/linear" paseo plugin ls ``` -Open **Linear** from an agent tab or the Command Center. +Open **Linear** from the workspace **+** menu or the Command Center. ## Configure the API key diff --git a/plugins/linear/contracts.ts b/plugins/linear/contracts.ts index e820e25..ee909f0 100644 --- a/plugins/linear/contracts.ts +++ b/plugins/linear/contracts.ts @@ -24,11 +24,16 @@ export const LinearCommentSchema = z.object({ createdAt: z.string(), user: z.string(), }); +export const LinearAssigneeSchema = z.object({ + id: z.string(), + name: z.string(), +}); export const LinearIssueSchema = LinearIssueSummarySchema.extend({ description: z.string(), teamId: z.string(), teamName: z.string(), - assignee: z.string().nullable(), + assignee: LinearAssigneeSchema.nullable(), + assignees: z.array(LinearAssigneeSchema), comments: z.array(LinearCommentSchema), states: z.array(LinearStateSchema), }); @@ -75,6 +80,11 @@ export const LinearMutationSchema = z.discriminatedUnion("type", [ issueId: z.string().min(1), priority: z.number().int().min(0).max(4), }), + z.object({ + type: z.literal("assignee"), + issueId: z.string().min(1), + assigneeId: z.string().min(1).nullable(), + }), ]); export type LinearMutation = z.infer; export const MutateLinearIssueRpc = defineRpc({ diff --git a/plugins/linear/external-url.test.ts b/plugins/linear/external-url.test.ts new file mode 100644 index 0000000..491a0bc --- /dev/null +++ b/plugins/linear/external-url.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, test } from "bun:test"; +import { openExternalLinearUrl } from "./external-url"; + +describe("external Linear issue links", () => { + test("prefers Paseo desktop's system-browser bridge", async () => { + const calls: string[] = []; + await openExternalLinearUrl( + "https://linear.app/example/issue/ENG-42/fix-sync", + { + platform: "web", + desktopOpen: (url) => { + calls.push(`desktop:${url}`); + }, + browserOpen: (url) => { + calls.push(`browser:${url}`); + }, + nativeOpen: (url) => { + calls.push(`native:${url}`); + }, + }, + ); + + expect(calls).toEqual([ + "desktop:https://linear.app/example/issue/ENG-42/fix-sync", + ]); + }); + + test("opens a new browser tab outside Electron", async () => { + const calls: string[] = []; + await openExternalLinearUrl("https://linear.app/example/issue/ENG-42", { + platform: "web", + browserOpen: (url) => { + calls.push(url); + }, + nativeOpen: () => { + calls.push("native"); + }, + }); + + expect(calls).toEqual(["https://linear.app/example/issue/ENG-42"]); + }); + + test("refuses non-http protocols", async () => { + const calls: string[] = []; + await expect( + openExternalLinearUrl("file:///etc/passwd", { + platform: "web", + browserOpen: (url) => { + calls.push(url); + }, + nativeOpen: (url) => { + calls.push(url); + }, + }), + ).rejects.toThrow("Only HTTP and HTTPS links can be opened"); + expect(calls).toEqual([]); + }); +}); diff --git a/plugins/linear/external-url.ts b/plugins/linear/external-url.ts new file mode 100644 index 0000000..7f709c6 --- /dev/null +++ b/plugins/linear/external-url.ts @@ -0,0 +1,35 @@ +export type ExternalUrlPlatform = "ios" | "android" | "web"; +export type ExternalUrlOpener = (url: string) => void | Promise; + +export interface ExternalUrlAdapters { + platform: ExternalUrlPlatform; + desktopOpen?: ExternalUrlOpener; + browserOpen: ExternalUrlOpener; + nativeOpen: ExternalUrlOpener; +} + +/** Opens an HTTP(S) Linear issue through the host OS instead of Paseo's browser pane. */ +export async function openExternalLinearUrl( + value: string, + adapters: ExternalUrlAdapters, +): Promise { + if (!isHttpUrl(value)) { + throw new Error("Only HTTP and HTTPS links can be opened."); + } + + if (adapters.platform === "web") { + await (adapters.desktopOpen ?? adapters.browserOpen)(value); + return; + } + + await adapters.nativeOpen(value); +} + +function isHttpUrl(value: string): boolean { + try { + const protocol = new URL(value).protocol; + return protocol === "http:" || protocol === "https:"; + } catch { + return false; + } +} diff --git a/plugins/linear/index.ts b/plugins/linear/index.ts index 188c08e..1a9b2f5 100644 --- a/plugins/linear/index.ts +++ b/plugins/linear/index.ts @@ -24,14 +24,14 @@ export default function contribute(plugin: PluginContext) { id: "linear", title: "Linear", icon: "CircleDot", - context: "agent", + context: "workspace", Component: LinearPanel, }); plugin.addCommandCenterItem({ id: "open-linear", title: "Open Linear", icon: "CircleDot", - context: "agent", + context: "workspace", onSelect: ({ openPanel }) => openPanel("linear"), }); return () => {}; diff --git a/plugins/linear/linear.server.ts b/plugins/linear/linear.server.ts index 9866975..cc03ad5 100644 --- a/plugins/linear/linear.server.ts +++ b/plugins/linear/linear.server.ts @@ -59,10 +59,14 @@ export async function mutateIssue( const payload = await issue.update({ stateId: mutation.stateId }); if (!payload.success) throw new Error("Linear did not update the issue state."); - } else { + } else if (mutation.type === "priority") { const payload = await issue.update({ priority: mutation.priority }); if (!payload.success) throw new Error("Linear did not update the issue priority."); + } else { + const payload = await issue.update({ assigneeId: mutation.assigneeId }); + if (!payload.success) + throw new Error("Linear did not update the issue assignee."); } return toDetail(await client.issue(issue.id)); }); @@ -131,13 +135,29 @@ async function toDetail(issue: Issue): Promise { issue.comments({ first: 50 }), ]); if (!team) throw new Error("The Linear issue has no team."); - const states = await team.states({ first: 100 }); + const [states, members] = await Promise.all([ + team.states({ first: 100 }), + team.members({ + first: 100, + includeArchived: false, + includeDisabled: false, + }), + ]); return { ...(await toSummary(issue)), description: issue.description ?? "", teamId: team.id, teamName: team.name, - assignee: assignee?.name ?? null, + assignee: assignee ? { id: assignee.id, name: assignee.name } : null, + assignees: members.nodes + .filter( + (member) => + member.isAssignable && + member.active && + member.archivedAt === undefined, + ) + .map((member) => ({ id: member.id, name: member.name })) + .sort((left, right) => left.name.localeCompare(right.name)), comments: await Promise.all( comments.nodes.map(async (comment) => ({ id: comment.id, diff --git a/plugins/linear/linear.test.ts b/plugins/linear/linear.test.ts index b86e5b0..41f6839 100644 --- a/plugins/linear/linear.test.ts +++ b/plugins/linear/linear.test.ts @@ -20,6 +20,10 @@ const issue: LinearIssue = { teamId: "t", teamName: "Engineering", assignee: null, + assignees: [ + { id: "ada", name: "Ada Lovelace" }, + { id: "grace", name: "Grace Hopper" }, + ], comments: [], states: [{ id: "done", name: "Done", type: "completed", color: "#fff" }], }; @@ -52,6 +56,19 @@ describe("Linear integration", () => { body: "Please verify the retry path.", }), ).toContain("Please verify"); + expect( + describeMutation(issue, { + type: "assignee", + issueId: "i", + assigneeId: "ada", + }), + ).toContain("Unassigned to Ada Lovelace"); + expect( + describeMutation( + { ...issue, assignee: { id: "ada", name: "Ada Lovelace" } }, + { type: "assignee", issueId: "i", assigneeId: null }, + ), + ).toContain("Ada Lovelace to Unassigned"); }); test("surfaces Linear failures without exposing credentials", async () => { diff --git a/plugins/linear/main.client.tsx b/plugins/linear/main.client.tsx index 8a9ff1f..25de549 100644 --- a/plugins/linear/main.client.tsx +++ b/plugins/linear/main.client.tsx @@ -1,4 +1,4 @@ -import type { PluginAgentPanelProps, PluginTheme } from "@getpaseo/plugin"; +import type { PluginTheme, PluginWorkspacePanelProps } from "@getpaseo/plugin"; import { useRpc } from "@getpaseo/plugin"; import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import React, { useEffect, useMemo, useState } from "react"; @@ -22,6 +22,16 @@ import { type LinearMutation, } from "./contracts"; import { describeMutation } from "./mutations"; +import { openExternalLinearUrl } from "./external-url"; +import { MarkdownContent, type MarkdownStyles } from "./markdown.client"; + +declare global { + interface Window { + paseoDesktop?: { + opener?: { openUrl?: (url: string) => Promise }; + }; + } +} const PRIORITIES = ["No priority", "Urgent", "High", "Normal", "Low"] as const; @@ -30,7 +40,7 @@ type Notice = { text: string; }; -export function LinearPanel({ theme, layout }: PluginAgentPanelProps) { +export function LinearPanel({ theme, layout }: PluginWorkspacePanelProps) { const statusRpc = useRpc(LinearStatusRpc); const searchRpc = useRpc(SearchLinearIssuesRpc); const getIssue = useRpc(GetLinearIssueRpc); @@ -44,6 +54,7 @@ export function LinearPanel({ theme, layout }: PluginAgentPanelProps) { const [notice, setNotice] = useState(null); const [selectingId, setSelectingId] = useState(null); const [updating, setUpdating] = useState(false); + const [pageIndex, setPageIndex] = useState(0); const status = useQuery({ queryKey: ["linear-status"], queryFn: () => statusRpc({}), @@ -58,7 +69,10 @@ export function LinearPanel({ theme, layout }: PluginAgentPanelProps) { }); useEffect(() => { - const timer = setTimeout(() => setDebouncedQuery(query), 220); + const timer = setTimeout(() => { + setDebouncedQuery(query); + setPageIndex(0); + }, 220); return () => clearTimeout(timer); }, [query]); useEffect(() => { @@ -67,7 +81,44 @@ export function LinearPanel({ theme, layout }: PluginAgentPanelProps) { return () => clearTimeout(timer); }, [notice]); - const issueItems = issues.data?.pages.flatMap((page) => page.items) ?? []; + const issueItems = issues.data?.pages[pageIndex]?.items ?? []; + + async function nextPage() { + const loadedPages = issues.data?.pages.length ?? 0; + if (pageIndex + 1 < loadedPages) { + setPageIndex((current) => current + 1); + return; + } + if (!issues.hasNextPage) return; + + const result = await issues.fetchNextPage(); + if (!result.isError && (result.data?.pages.length ?? 0) > loadedPages) { + setPageIndex((current) => current + 1); + } + } + + async function openIssueInBrowser(url: string) { + setNotice(null); + try { + const desktopOpen = + typeof window !== "undefined" && + typeof window.paseoDesktop?.opener?.openUrl === "function" + ? (value: string) => window.paseoDesktop?.opener?.openUrl?.(value) + : undefined; + await openExternalLinearUrl(url, { + platform: layout.platform, + desktopOpen, + browserOpen: (value) => { + if (typeof window !== "undefined") { + window.open(value, "_blank", "noopener,noreferrer"); + } + }, + nativeOpen: Linking.openURL, + }); + } catch (error) { + setNotice({ tone: "danger", text: message(error) }); + } + } async function select(id: string) { setSelectingId(id); @@ -160,6 +211,7 @@ export function LinearPanel({ theme, layout }: PluginAgentPanelProps) { onQueue={queue} onCancel={() => setPending(null)} onConfirm={confirm} + onOpenExternal={openIssueInBrowser} styles={styles} /> ) : ( @@ -170,11 +222,17 @@ export function LinearPanel({ theme, layout }: PluginAgentPanelProps) { loading={issues.isLoading} error={issues.isError ? message(issues.error) : null} selectingId={selectingId} - hasNextPage={issues.hasNextPage} - fetchingNextPage={issues.isFetchingNextPage} + pageNumber={pageIndex + 1} + hasPreviousPage={pageIndex > 0} + hasNextPage={ + pageIndex + 1 < (issues.data?.pages.length ?? 0) || + Boolean(issues.hasNextPage) + } + changingPage={issues.isFetchingNextPage} onQueryChange={setQuery} onSelect={select} - onLoadMore={() => issues.fetchNextPage()} + onPreviousPage={() => setPageIndex((current) => current - 1)} + onNextPage={nextPage} styles={styles} /> )} @@ -189,11 +247,14 @@ function IssueSearch({ loading, error, selectingId, + pageNumber, + hasPreviousPage, hasNextPage, - fetchingNextPage, + changingPage, onQueryChange, onSelect, - onLoadMore, + onPreviousPage, + onNextPage, styles, }: { query: string; @@ -202,11 +263,14 @@ function IssueSearch({ loading: boolean; error: string | null; selectingId: string | null; + pageNumber: number; + hasPreviousPage: boolean; hasNextPage: boolean; - fetchingNextPage: boolean; + changingPage: boolean; onQueryChange: (value: string) => void; onSelect: (id: string) => void; - onLoadMore: () => Promise; + onPreviousPage: () => void; + onNextPage: () => Promise; styles: Styles; }) { return ( @@ -250,14 +314,28 @@ function IssueSearch({ /> )} - {hasNextPage ? ( -