Repository navigation
Conversation
Open
ashx-j
force-pushed
the
feat/plugins-phase-1
branch
from
October 30, 2026 16:16
3383ffe to
40fbace
Compare
…bility flag Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PluginHost discovers installed and dev plugins, loads enabled CommonJS entries after the server is listening, and hands each a Promise-based API for logging, settings, storage, RPC methods and channels. Failures mark the plugin failed and never stop the server. Settings live beside T3's state with secrets in the server secret store; storage is a lazily opened SQLite file. Eight plugins.* WebSocket methods manage plugins, and the environment advertises plugins: true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An adversarial review found that reloading one plugin left plugins whose folders vanished running, that a caller disconnecting mid-disable left a plugin half torn down but still routed, that a misspelled access level made a write method callable by read-only sessions, and that reload kept old code for symlinked plugin folders. Lifecycle changes now run in the host's scope, vanished plugins are stopped, access must be read or write, and discovery resolves real paths. Activation and cleanups get deadlines, calls stop routing before teardown, and plugins.json keeps unknown keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ashx-j
force-pushed
the
feat/plugins-phase-1
branch
from
October 31, 2026 21:32
40fbace to
2918b9f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 of the plugin system roadmap (#3, phase issue #5). It builds on #14, so until that merges this diff also shows Phase 0's two commits.
After Phase 0 a plugin could be described, but nothing ran it. Now the server finds plugins, runs the ones the user enabled, and lets clients manage them.
PluginHostfinds plugins in<stateDir>/plugins/<id>/and in dev folders listed in<stateDir>/plugins.json. It loads each enabled plugin's CommonJS entry and calls itsactivatewith a Promise-based API: logging, settings, storage, RPC methods, publish channels and cleanup hooks. Each plugin activates in its own fiber after the server is listening. A plugin that throws while loading or activating is marked failed with the stage and message. A plugin method that throws fails only that call. In both cases the server and other plugins keep running.<stateDir>/plugin-data/<id>/settings.jsonand validated against the manifest's JSON Schema using Effect's own importer, so there is no new dependency. Fields markedformat: "t3-secret"go to the server secret store and read back redacted. Storage is a key-value table in<stateDir>/plugins.sqlite, opened on first use.plugins.*WebSocket methods: list, subscribeStates, setEnabled, reload, settings.get, settings.update, invoke and subscribe. Enabling, reloading and changing settings needsettings:write, since an enabled plugin runs code in the server. Calling a method a plugin registered as a write needsorchestration:operate.plugins: true.Changes from the roadmap (the issues are updated to match):
plugin-data/<id>/, not inside the plugin's own folder. Otherwise replacing a plugin's files could lose its settings, and a dev plugin's settings folder would show up as a broken installed plugin.requiremade fromprocess.execPathloads files from disk in the packaged single executable and in plain Node, so there is no separateimport()path. I verified it in both.plugins.subscribeStates, not an opt-in on the server config stream. Clients open the config stream once per connection, so a settings page would have needed a second subscription that resends the whole config.t3_plugins_listMCP tool moved to Phase 3. Every agent turn pays for the advertised tool list, so it will appear only while a plugin is installed.Fixes from an adversarial review (a second Claude Opus 5.5 read the first version of this PR looking for bugs). Each confirmed bug has a test that fails without the fix:
accessvalue such as"Write"made a write method callable by read-only sessions. Only"read"and"write"are accepted now.activatehas a 30-second deadline, all of a plugin's cleanups share one 5-second deadline, calls stop reaching a plugin before its cleanups run, and in-flight calls end when it is disabled.plugins.jsonkeeps keys it doesn't know, and a malformed file now makes reload fail with a reason instead of quietly disabling everything.Measurements:
Listening on, on a copy of real data, 5 runs of each build alternating. Phase 0 had a median of 5020 ms (range 4930 to 5297) and this PR has a median of 5031 ms (range 4891 to 5054). That difference is within run-to-run noise.subscribeServerConfigsnapshot: the capabilities part grew from 1473 to 1488 bytes, which is the"plugins":trueflag. The stream sends no new events.plugins.sqliteis never created, and agents see no new MCP tools.Test plugins:
hello-plugin. Itspingreads a setting. Itscountwrites to storage, needs write access and publishes on a channel. It republishes its settings when they change, and its cleanup writes a storage flag.broken-plugin. Itsactivatethrows.Verification:
PluginHost.test.ts(10 cases),pluginDiscovery.test.ts,PluginSettingsStore.test.ts,PluginStorage.test.tsandRpcAuthorization.test.tspass, andvp run check:changedpassed. I also ran a real server against this worktree's seeded data, and the packaged single executable against a scratch home. In both, the hello plugin activated,plugins.invoke("ping")returned{"pong":"from verify","greeting":"hello"}, a settings update returned the secret as••••••, and after disabling,pingfailed withPlugin hello-plugin is disabled.PR filed by Claude Opus 5.5 in Claude Code through T3 Code. A Claude Opus 5.5 subagent wrote the settings and storage services.