Repository navigation
Conversation
This was referenced Oct 30, 2026
This was referenced Oct 30, 2026
This was referenced Oct 30, 2026
Open
…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>
Settings gains a Plugins page for the selected environment: each plugin with its status, failure, enable switch and reload, plus a settings form generated from the manifest's JSON Schema with write-only secret fields and a JSON fallback. The page follows plugins.subscribeStates only while open; the form model lives in client-runtime for the later mobile screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng and send hooks Plugins can now follow stored orchestration events by type, read and act on threads and projects, run scheduled work, see and extend outgoing messages before they are sent, offer MCP tools to agents and serve HTTP routes under /api/plugins/<id>, and read the server's version, descriptor and redacted settings. The send hook runs in ThreadManagementService.dispatch with a 2 s deadline and fails open; with no hooks it is one Ref read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… writes An adversarial review found that a send hook appending to /goal clear turned it into setting a goal, that the SDK promised a veto nothing honoured, that several event types merged out of sequence order, that a schedule could outlive its plugin, and that plugin write tools skipped the caller's mode limits. Hooks now skip slash commands, veto is gone from the SDK, multi-type event streams stay ordered, schedules and hooks stop on the deactivation signal, API calls end after cleanups, plugin write tools need full access like environment writes, and tool schemas must be objects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plugins could only run on the server. The server now serves each active plugin's web bundle, and the web app loads it once an active plugin has a web part: a starter follows plugin state after idle, and the loader fetches bundles with the connection's credentials and imports them from blob URLs. Bundles share the app's React, atom hooks and UI kit through a host object. Plugins can add right panels, listed in the add menu and the empty panel, and replace their generated settings form. An unloaded plugin's panel shows "Plugin not active" with a close button. The desktop CSP allows blob scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web plugins could add panels but nothing users could run from the keyboard. Plugins can now register command palette commands that users bind as plugin.<pluginId>.<commandId>, and slash commands in the composer that run plugin code. They can also navigate and load files from their assets folder, which the server serves under /api/plugins/<id>/assets/*. The SDK gains a Vite preset that builds web.js against T3's React and UI kit, keeping only the names a plugin uses, and typed UI components. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…om plugins Web plugins could add panels and commands but not change how T3 looks or how conversations read. A plugin's themes/ folder is now published through the environment theme service, so its themes reach every client. Web plugins can show composer banners, draw their own MCP tool calls in the timeline, draw fenced code of a language in chat messages, and inject styles. Everything is removed when the plugin unloads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tools Plugins could see a message before it was sent but not what happened after, and agents could list plugins but not use them. turns.afterRunCompleted delivers every ended run in order, saving its position in the plugin's storage so runs that end while the plugin is off arrive when it starts. turns.beforeDispatch now gets the message id, attachment summaries and the requested model. Agents get t3_plugins_invoke and t3_plugins_set_enabled, gated as writes and only advertised once a plugin is installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plugins had to be copied into the data folder by hand or listed as dev folders. plugins.install now takes a folder on the server or a git repository, stages it, checks the manifest and swaps it into place with a rename, keeping the previous version if anything fails. plugins.uninstall stops and deletes a plugin, keeping its data unless asked otherwise. Settings → Plugins gets an Add plugin form and a remove action, agents get t3_plugins_install and t3_plugins_uninstall, a user guide explains it, and the SDK gets a README for plugin authors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An adversarial review of the plugin stack found 10 issues. Uninstalling a symlinked plugin deleted the folder behind the link. afterRunCompleted delivered interrupted runs twice and could skip runs while catching up. A cancelled install could race its own swap, and a crash mid-swap lost the plugin. The web loader leaked registrations made after unload, let one stuck plugin block the rest, kept stale code after a same-version reload, and added a shortcut handler before any plugin command existed. Plugin tool renderers only matched Codex tool names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ashx-j
force-pushed
the
feat/plugins-review-fixes-2
branch
from
October 31, 2026 21:32
35260e5 to
68ee54c
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.
Fixes from a second adversarial review, of #23 to #25 (#3). It builds on #25, so until #14 to #25 merge this diff also shows their commits.
A second Claude Opus 5.5 at xhigh effort reviewed #23 to #25 from their code alone and found 10 issues. All 10 are fixed here. It also confirmed the tarball extractor never writes outside the plugin's folder.
Installs (#22, #24, #25):
updatealso only checked the id after swapping. Now install refuses an id that is already installed from a different source, and update checks the id before anything moves.https://token@host/…) was shown to every client that can read plugin state, including read-only sessions and agents. Plugin state now shows such URLs with the credentials replaced; the server keeps the full URL for updates.npm (#25):
content-lengthover 32 MiB is refused before reading, and metadata uses npm's abbreviated document, which is much smaller.sizerecords, reads the ustar prefix field only from ustar headers, needs the 2 zero blocks that end an archive, and refuses a path that appears twice.Run hook (#23):
afterRunCompletedfailed (a slow handler can make the server end it), the hook stopped silently until the plugin restarted. It now logs the failure and resumes from its saved position after a second.Web runtime (#23):
awaitnever settled still blocked every other plugin. The import now has the same 30-second deadline as fetch andactivate.revisionrestarted at 0 with every server process, so after a restart a client could keep a plugin's old web code. It now starts from the clock.Verification:
pluginTarball.test.tsadds a header with a bad checksum and a duplicate path, both refused. Its test tarballs now carry real checksums.PluginHost.test.tsinstall case adds a folder claiming an installed plugin's id (refused) and an update of a disabled plugin, which stays disabled.api/turns.test.tspasses.vp run check:changedpassed.PR filed by Claude Opus 5.5 in Claude Code through T3 Code.