Skip to content

build: extract @copse/plugin-sdk, the plugin runtime SDK and host protocol - #2335

Merged
jonathanKingston merged 1 commit into
mainfrom
claude/extract-plugin-sdk
Sep 4, 2026
Merged

jonathanKingston merged 1 commit into
mainfrom
claude/extract-plugin-sdk

Conversation

@jonathanKingston

Copy link
Copy Markdown
Collaborator

Summary

  • Adds packages/plugin-sdk (@copse/plugin-sdk): plugin-tool-protocol, plugin-tool-sdk, plugin-tool-worker, plugin-tool-source, plugin-tool-snapshot, plugin-browser-service, and plugin-model-turn from src/main/services/plugins/; mcp-schema and mcp-config from src/main/services/mcp/; and the MCP configuration types from src/shared/types/mcp.ts. About 1,800 LOC plus tests. Depends on @copse/agent, @copse/llm, @copse/std, zod; no host-app imports.
  • Introduces configurePluginSdk for the one host fact the snapshot helper needs, the profile root reviewed snapshots live under (default COPSE_DIR or ~/.copse). The app binds copseDataRoot in src/main/services/plugins/plugin-sdk-environment.ts, imported for its side effect by the app-side re-exports.
  • The standalone worker bundle keeps its entry path. src/main/services/plugins/plugin-tool-worker.ts is now a one-line entry importing the package worker, so scripts/main-bundles.mts, the stdout-protocol guard's entry list, and the host's join(__dirname, 'plugin-tool-worker.js') lookup are unchanged.
  • Every previous import path is a re-export of the package; the plugin-tool-sdk stub had no remaining importers and was deleted. custom-tools-config stays in the app since it depends on the app's ToolDefinition contract; the registry service, sandboxed tool host and controller, disk discovery, browser panel, MCP registry, and curated list stay as the host.
  • docs/plugins.md's module layout names the package; package README added.

Closes #2309. Step 6 of the library-splits plan (#2303). Branches from main; all four earlier extractions are already merged.

Behaviour is unchanged

No protocol message, schema, or snapshot rule changed. The moved tests (plugin-tool-sdk, plugin-tool-source, plugin-tool-snapshot, plugin-model-turn, mcp-schema, mcp-config) pass unmodified inside the package; the host tests (plugin-tool-host, plugin-tool-controller, plugin-service, mcp-registry-persistence, custom-tools-registry, plugin-browser-panel) pass unmodified through the re-exports.

Validation

  • pnpm test -- workspace-package-invariants pnpm-workspace-invariants module-boundaries stdout-protocol-guard settings-read-keys packages/plugin-sdk/ src/main/services/plugins/ src/main/services/mcp/ permission-gate — 283 pass
  • pnpm test (full unit suite) — 8,253 pass
  • pnpm run typecheck, pnpm run lint, pnpm run check:dead-code, pnpm run build — clean
  • Build plumbing and an equal-behaviour move; no user-visible change, so no visual evidence is required.

🤖 Generated with Claude Code

…tocol

Moves the plugin-tool wire protocol, the author-facing SDK, the isolated
worker entry, runtime source discovery and reviewed snapshots, the
browser-service and model-turn adapters, and the MCP server configuration
schema and types into packages/plugin-sdk (@copse/plugin-sdk). Registered
through the workspace protocol; depends on @copse/agent, @copse/llm,
@copse/std, and zod; no host-app imports.

The one host fact the snapshot helper needs, the profile root the reviewed
snapshots live under, becomes configurePluginSdk (default COPSE_DIR or
~/.copse); the app binds copseDataRoot in plugin-sdk-environment.ts. The
standalone worker bundle keeps its entry path: src/main/services/plugins/
plugin-tool-worker.ts is now a one-line entry that imports the package
worker, so scripts/main-bundles.mts, the stdout-protocol guard, and the
host's dist lookup are unchanged.

Every previous import path is a re-export of the package; the one stub with
no remaining importers (plugin-tool-sdk) was deleted. custom-tools-config
stays in the app because it depends on the app's ToolDefinition contract.
docs/plugins.md's module layout names the package.

Closes #2309. Part of #2303.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🖥️ PR preview

@copse-release-bot

Copy link
Copy Markdown
Contributor

Reference screenshots ready for review

Review GitHub’s image diffs in screenshot PR #2336.
Merge it (or enable auto-merge) to apply the accepted PNGs to claude/extract-plugin-sdk.

Rendered for 71c068f63e93 by CI run 33816195270; the immutable artifact is reference-screenshot-candidates-33816195270.

If this source branch moves, a later successful render closes the stale review PR and replaces this link.

@jonathanKingston
jonathanKingston merged commit 3b5bedc into main Sep 4, 2026
24 checks passed
@jonathanKingston
jonathanKingston deleted the claude/extract-plugin-sdk branch September 4, 2026 09:37
github-actions Bot added a commit that referenced this pull request Sep 4, 2026
jonathanKingston added a commit that referenced this pull request Sep 5, 2026
)

## Why

The screenshot review PR for #2335
([#2336](#2336)) re-rendered
19 reference PNGs with no UI change behind them. Pixel-diffing each pair
traced every band to something the frame read from the live runner,
repo, or clock instead of a fixture.

## What churned, and the fix

| Seen in the diffs | Cause | Fix |
| --- | --- | --- |
| `runner@runnervmgx7h7:~/work/…$` → `runner@runnervmejwal` | Runner rc
files set the bash prompt; hostname changes per run | `SHELL` =
`tests/e2e/fixtures/e2e-shell.sh` under e2e (bash `--norc --noprofile`,
prompt `$ `) |
| Ports rail `:9647 ss`, `:33569 electron`, `:36355 chromedriver` | Live
host port scan | `listPortRows` reports nothing listening under
`COPSE_E2E` unless a spec seeds rows, so the rail hides |
| Context wheel 10% → 11% (in almost every shot) | Pre-send estimate
counts the checkout's `AGENTS.md` + skills; specs open `process.cwd()` |
`seedStableWorkspace()`: fixed temp project, few files, one commit at a
fixed date. Migrated the specs behind these shots |
| Changes rail listing CI's re-rendered `*.png` as `M` | Same: the
checkout is the project | Same |
| `@shell` picker results from the real tree | Same | Same |
| `…selection-ZZvJEL ⤳ work` in the titlebar | `mkdtemp` suffix | Same
(fixed leaf name) |
| `http://127.0.0.1:44701/reference` | `listen(0)` |
`listenOnFixturePort(server, port)` — fixed per-spec port, ephemeral
fallback |
| `Background task 84e03bbc-… exited` | `randomUUID()` in the wake
message | Sequential ids under `COPSE_E2E` (`nextBackgroundOperationId`)
|
| Sidebar `New Thread` vs `Mock response to: …` | Auto-title lands an
IPC round trip after the turn | `waitForActiveThreadTitle()` before the
capture |
| Settings shots shifted ~8px / ~80px | Smooth scroll captured
mid-flight | `saveAppScreenshot`/`saveElementScreenshot` wait for scroll
offsets and the subject's box to hold still (`waitForSettledLayout`) |
| `Latest · Sep 3, 2026, 12:08 AM` | Run the real scheduler just fired;
no fixture can seed it | `pinTextForCapture` swaps that one text node
for the capture and restores it; the DOM assertion on the real text
stays |
| Changes pop-out `Unstaged (16)` with `.bashrc`, `.gitconfig`,
`.claude/…` | bubblewrap materialises every missing mandatory-deny path
as an empty bind-mount target; ASRT defers cleanup while another
sandboxed command runs, so a concurrent `git status` (main window +
pop-out) lists them | `waitForFixtureRows()` in
`git-changes-popout-embed.e2e.ts` waits for the Changes list to settle
on the four files the fixture seeds before each capture. **Spec-side
only** — see below |

`docs/testing-strategy.md` → "Deterministic screenshots" gains the
matching rules.

## The one product-behaviour change that was here, and is not any more

An earlier revision filtered those bind-mount targets out of
`getGitStatus` (`withoutGhostMountPoints`). Review found the flaw: it
ran on every Linux `git status` and identified files by path and
emptiness, so a user's own zero-byte `.gitconfig` or `.bashrc` would be
hidden even where no sandbox had created one. Name and size are not
provenance.

Recording the paths bwrap actually materialised is the sound version,
and it is not viable today: `@anthropic-ai/sandbox-runtime` keeps
`bwrapMountPoints` module-private, so the record would have to be ours
at wrap time — and `afterSandboxedCommand` is not called by
`spawnBackgroundProcess` or `spawnPtyInProjectSandbox`, so an in-flight
counter would drift upward and suppress forever. Balancing that
lifecycle is its own change, not a screenshot PR.

Reverted in 6747fed. The product reports exactly what git reports; the
determinism moved into the spec that needed it. The underlying transient
remains for Linux users during overlapping sandboxed commands and
deserves a separate issue against the sandbox layer.

## Validation

- `pnpm run typecheck`, `lint`, `format:check`, `check:dead-code`,
`check:oracle`, `check:e2e-syntax` green; new unit tests for the ports
seam and background ids.
- Ran the affected specs locally under Xvfb: `acp-stale-model-picker`
(element-typed capture), `terminal-display`, `shell-mention`,
`settings-styling`, `settings-usage-plan-worth-it`,
`settings-usage-value-map-card-link`, `background-task-lifecycle`,
`monaco-selection-chat`, `thread-terminal-rename-archive`,
`project-switch-panels`, `portrait-panel-controls`,
`automation-trigger`, `automation-attention` and
`git-changes-popout-embed` pass.
- The oracle plans a **full** e2e run for this diff, so the whole
reference set re-renders once; the review PR the bot opens touches every
shot with a terminal rail or a context wheel.

## Not done here

`seedEmptyProject(process.cwd(), …)` remains in ~160 other specs. Their
wheels still track the real `AGENTS.md`; migrating them is mechanical
but each needs a look at whether it relies on repo contents.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01JNuSTyzLnTCSRoYzbCZGzx

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jonathan Kingston <KingstonMailBox@gmail.com>

This branch was previously deployed

1 inactive deployment
github-pages — 71c068f6 Deployed Sep 4, 2026 by jonathanKingston via deploy / deploy #852
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract @copse/plugin-sdk: plugin tool protocol, SDK, and worker

1 participant