Use core v0.59.0: plugin files hold their configuration, one editor - #272
Merged
Merged
Conversation
…mations
Plugins contract addendum 4 (core v0.59.0, protocol 35). On error, scope and
setting values live in the plugin's manifest; config keeps only the file,
its hash and the switch. Built against provisional types until core ships.
Editor
- Clicking a plugin opens one editor with two tabs and a single Save:
- Settings: the enable switch (an app toggle, not in the code), on error
(Segmented), scope and the plugin's settings form.
- Code: the current code in an editable CodeMirror, with "Import from
file…" at the top right.
- Form edits go through PluginRewrite into the code. Code edits go through
PluginInspect (debounced) into the form. Syntax and manifest errors show
inline under the line, with line and column, and lock the form until the
code loads again. Late answers never overwrite newer edits; switching tabs
and saving flush both directions first.
- Unsaved changes survive tab switches (both panes stay mounted, so the
code keeps its undo history). Closing (Esc, ×, Cancel, outside click) or
leaving for Review changes with unsaved changes asks once, in the app.
- Scope inputs take `*` patterns and suggest known clients, models and
upstreams; a typed pattern lists the known names it matches.
- The header shows the SHA-256 of the code being edited (the native dialog
shows the same) and marks permissions the edited code adds.
- The separate "Replace code" dialog and Settings dialog are gone. A plugin
that fails to load, and a changed file that cannot load, offer "Edit
code" instead (the latter edits the file on disk).
Confirmations (§3)
- A native dialog appears only when a plugin holding reply_tool_calls (in
the old or new code) is installed, turned on, has its code changed or has
an on-disk change approved. Data-only saves, other plugins, turning off,
reordering never ask; Delete keeps its in-app confirmation.
- The webview decides from the permissions it has: certain cases go
straight to the native command, the rest call the plain endpoint
(CreatePlugin, SavePlugin, ApprovePluginFile) and fall back to the native
command on 403 control.plugin.needs_confirmation.
- Install: the in-app review shows the code, permissions, the scope and on
error written in the code, and the ID; one click on Install (plus the
native dialog for a tool-call plugin). No second review.
- The row switch sends the approved code with the new state (SavePlugin).
Rust
- plugin_install_confirmed, plugin_save_confirmed and
plugin_approve_confirmed replace plugin_install, plugin_replace_source,
plugin_approve and plugin_update_confirmed. Each re-inspects through core,
shows one native dialog and calls the matching …Confirmed endpoint. The
save dialog lists what changes: turning on, code (old → new SHA-256), or
each data change when a rewrite of the approved code reproduces the save.
- CreatePlugin, SavePlugin, ApprovePluginFile and PluginRewrite join both
webview allow-lists; the three …Confirmed endpoints stay out (tested).
Provisional
- src/plugins/api.provisional.ts and src-tauri/src/plugins/wire.rs hold
the addendum-4 types and endpoints, with the swap steps for integration.
The control-plane test for tool-call plugins is ignored until v0.59.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reorder dialog takes drag and drop as well as the up and down buttons. A row is dragged by its grip and name (the buttons stay outside the drag area), the dragged row dims and an insertion line shows where it lands. It reuses the pointer-event hook of the route and group dialogs (routing/useReorder), so there is no new dependency and Tauri's file-drop handling does not interfere. The buttons stay for the keyboard. When a move takes a plugin to the top or the bottom, the button that just became disabled hands focus to the other one, so the keyboard can keep going. Saving still writes one config version (ReorderPlugins). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Swap the provisional addendum-4 types for the ones core main generates (550e6fd, future v0.59.0, CONTROL_API_VERSION 35). - src/generated/tw-api.ts is regenerated. src/plugins/api.provisional.ts and src-tauri/src/plugins/wire.rs are gone: the webview endpoint list and the three native confirmations use tw_api::ep and tw_api's PluginView, PluginInspection, ManifestView, SettingSpecView, PluginCreate, PluginSave and PluginRewriteRequest directly. - Differences from the provisional guesses: PluginRewrite answers PluginSource (there is no PluginRewritten), its request type is PluginRewriteRequest, and its settings carry only the keys to change. The editor and the native save dialog keep sending every declared setting, which core accepts. - core.zh.json: gw.plugin.manifest_not_data and gw.plugin.manifest_not_data_at added, config.plugin.blank_pattern and config.plugin.setting_type removed, control.plugin.needs_confirmation rewritten for core's new sentence (install, turn on, change the code or approve a change to the file). - The control-plane test for tool-call plugins runs again. The tw-* pin stays at v0.58.0 until core tags v0.59.0, so until then this branch builds only with a local [patch] of the core crates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tory
Core now refuses PUT /config, PATCH /config and a rollback with 403
control.plugin.needs_confirmation when the write would turn on, add or
re-approve a plugin that can change the tool calls in replies, and those
paths have no confirmed variant. A toast disappeared after a few seconds
and did not say where the change can be made.
The config file editor and the version history now show core's sentence
in an inline notice ("This change has to be confirmed on the Plugins
page", plus whether the file was saved or the version restored) with a
button that opens the Plugins page. Other failures still use the toast.
PatchConfig is only used for retention, failover and client probes, so it
cannot hit this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core puts a plugin's manifest name into its messages ({plugin} in
gw.plugin.* and control.plugin.needs_confirmation, plugin_name in
plugin_failed). For the default plugins that name is English, so the
Chinese UI said "Convert WSL and Windows paths" where the Plugins page
says 「WSL 路径转换」.
One helper on each side now names a plugin everywhere: pluginName (TS)
and plugins::defaults::name_in (Rust), both reading
src/i18n/plugin-defaults.json. With an id, a plugin is matched by id and
manifest name as before; with only a name, as in a message's {plugin},
by the exact English manifest name. Descriptions and setting labels
still need the id.
- zhOf / core_text::zh render {plugin} through it, so toasts, notices,
inline errors (such as the config file's 403), the request drawer's
plugin runs, the stats tooltip and notifications all say the same
name. Callers that know the plugin pass its id (coreText, errorText).
- The history search sends the codes with {plugin} when the query
matches a localized default plugin name, since the stored sentences
are English.
- Shared cases in core.zh.cases.json keep both implementations in step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Add plugin dialog still had the old flow: a Choose a file / Paste code switch, a drop zone, Next and a separate review step. Adding now opens the plugin editor in a "new plugin" mode, so it works exactly like editing: - It opens on the Code tab with a short starter template that loads: a pure-data manifest written the way core rewrites it (the system permission, one note setting, on_error) and an onRequest hook that does nothing until the note is filled in. zh and en templates differ only in their text. - The code is edited in place, can be pasted, and "Import from file…" stays at the top right. The header (name, ID, SHA-256, permission chips) and the Settings tab follow the code through PluginInspect, with inline errors, as when editing. - The Settings tab adds a Plugin ID field. It follows the imported file name or the code's name until edited, and is checked for format, core's reserved words and existing IDs; suggestId now avoids reserved words the way core does. The enable switch starts off. - One primary button, Install. A plugin whose manifest holds reply_tool_calls goes straight to plugin_install_confirmed (one native confirmation, CreatePluginConfirmed); any other installs with CreatePlugin. There is no review step. - Closing with changes asks once, saying the plugin will not be installed. SourceDialog, its strings, the drop zone, the segmented switch and the review step are gone. Editor.test.tsx covers the new mode and the template; model tests cover the ID checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
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.
Moves ThinkWatch Lite to core v0.59.0 (ef10a8b, protocol 35) and lands the plugin editor built against it.
What users get
Plugins: the file is the configuration
*patterns) and behavior on errors live in its own JS file. The app writes them into the manifest and leaves the rest of the file byte for byte; only Enabled stays an app switch.config.yamlin the app or rolling back cannot do those: the config editor and the version history now show core's refusal inline, with a button that opens the Plugins page.Commits
plugins: the JS file is the source of truth, one editor, fewer confirmationsplugins: drag to reorderplugins: build against core's final plugin contract (protocol 35)config: keep core's plugin confirmation refusal in the editor and historyplugins: name default plugins in the UI language in core's messagesplugins: add a plugin in the same editor as editingUse core v0.59.0: every core crate pinned tov0.59.0,Cargo.lockre-locked withcargo metadataonly, so just the seven core crates move to ef10a8b.src/generated/tw-api.tsalready matched the tag.devat b0ee70c (README badge).shots: oracle answers from core v0.59.0Upgrade notes for the next release
security.hidden_textandsecurity.output_limitare removed. Aconfig.yamlthat still has either key does not load, and the app starts in safe mode. Lite 2026.10.0 writes them only when one of these settings was changed from its factory value.Checks
pnpm typecheck,pnpm test(744 tests),pnpm buildcargo fmt --check,cargo clippy --all-targets --locked -- -D warningscargo test --lockedagainst the published twcore v0.59.0 fromfetch-core.sh: 367 app tests,control_plane(9),msg_codes(5),ts_bindings(2), and the tw-adopt and tw-scan suites.UPDATE_TS=1leavessrc/generated/tw-api.tsunchanged.scripts/shots/core/oracle.shat v0.59.0: onlyapi_versionandversionin the status answers change.scripts/version.sh,scripts/release_notes_test.pytwcore init0.57.1 and a copy of a real 2026.10.0 config both passtwcore check0.59.0; a config withsecurity.hidden_textfails with the schema error, as the upgrade note says.🤖 Generated with Claude Code