From e52eba9fd95336a55dccfc8f6d06ff8d33e4e85f Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:10:36 +0800 Subject: [PATCH] chore: v0.59.0 Release for #276 and #277. - Bump the workspace version to 0.59.0 - release-notes/0.59.0.md The control-plane protocol (35) and the request store schema (25) are already at their release values on main and do not change here. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 38 ++++++++++++++--------------- Cargo.toml | 2 +- release-notes/0.59.0.md | 54 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 release-notes/0.59.0.md diff --git a/Cargo.lock b/Cargo.lock index b566731..4b70b57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,7 +3143,7 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.58.0" +version = "0.59.0" dependencies = [ "serde", "serde_json", @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "tw-bedrock" -version = "0.58.0" +version = "0.59.0" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -3175,7 +3175,7 @@ dependencies = [ [[package]] name = "tw-breaker" -version = "0.58.0" +version = "0.59.0" dependencies = [ "serde", "serde_json", @@ -3183,7 +3183,7 @@ dependencies = [ [[package]] name = "tw-config" -version = "0.58.0" +version = "0.59.0" dependencies = [ "blake3", "libc", @@ -3208,7 +3208,7 @@ dependencies = [ [[package]] name = "tw-control" -version = "0.58.0" +version = "0.59.0" dependencies = [ "axum", "base64", @@ -3249,7 +3249,7 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.58.0" +version = "0.59.0" dependencies = [ "serde", "serde_json", @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "tw-engine" -version = "0.58.0" +version = "0.59.0" dependencies = [ "serde", "serde_json", @@ -3270,7 +3270,7 @@ dependencies = [ [[package]] name = "tw-gateway" -version = "0.58.0" +version = "0.59.0" dependencies = [ "arc-swap", "async-stream", @@ -3319,7 +3319,7 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.58.0" +version = "0.59.0" dependencies = [ "base64", "bytes", @@ -3334,7 +3334,7 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.58.0" +version = "0.59.0" dependencies = [ "serde", "serde_json", @@ -3348,7 +3348,7 @@ dependencies = [ [[package]] name = "tw-observe" -version = "0.58.0" +version = "0.59.0" dependencies = [ "tokio", "tracing", @@ -3357,7 +3357,7 @@ dependencies = [ [[package]] name = "tw-plugin" -version = "0.58.0" +version = "0.59.0" dependencies = [ "libc", "rand 0.10.2", @@ -3372,7 +3372,7 @@ dependencies = [ [[package]] name = "tw-pricing" -version = "0.58.0" +version = "0.59.0" dependencies = [ "arc-swap", "flate2", @@ -3385,14 +3385,14 @@ dependencies = [ [[package]] name = "tw-secret" -version = "0.58.0" +version = "0.59.0" dependencies = [ "thiserror", ] [[package]] name = "tw-store" -version = "0.58.0" +version = "0.59.0" dependencies = [ "blake3", "bytes", @@ -3412,7 +3412,7 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.58.0" +version = "0.59.0" dependencies = [ "serde", "serde_json", @@ -3421,7 +3421,7 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.58.0" +version = "0.59.0" dependencies = [ "notify", "tempfile", @@ -3431,7 +3431,7 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.58.0" +version = "0.59.0" dependencies = [ "saphyr-parser", "serde_yaml_ng", @@ -3441,7 +3441,7 @@ dependencies = [ [[package]] name = "twcore" -version = "0.58.0" +version = "0.59.0" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 1f715bf..924493a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ categories = ["network-programming", "web-programming::http-server"] # 二进制走 CalVer,crate 走 SemVer —— 两者是两回事,卖给不同的人。 # 这里是 crate 的版本。 -version = "0.58.0" +version = "0.59.0" [workspace.dependencies] # ── 内部 crate ─────────────────────────────────────────── diff --git a/release-notes/0.59.0.md b/release-notes/0.59.0.md new file mode 100644 index 0000000..7604b6e --- /dev/null +++ b/release-notes/0.59.0.md @@ -0,0 +1,54 @@ +This release keeps a plugin's configuration in the plugin's own file. What a plugin does when it fails, which requests it handles and the values of its settings are written in the manifest of `plugins/.js`, which has to be plain data, and `config.yaml` keeps only the plugin's id, file, approved hash and whether it is on. Saving a plugin is one endpoint that tells a change of data from a change of code, and only plugins that can rewrite tool calls need a confirmation in the app. On Windows, `twcore.exe` no longer needs the Visual C++ Redistributable. + +**Upgrade notes** + +- The control-plane protocol version (`CONTROL_API_VERSION`) goes from 34 to 35. ThinkWatch Lite connects only to a core with the same protocol version. No ThinkWatch Lite release includes 0.58.0. ThinkWatch Lite 2026.10.0 includes 0.57.1 (protocol 31) and does not connect to 0.59.0. A server used with it stays on 0.57.1 until the app is updated to a release that includes 0.59.0; `sudo twcore upgrade --version 0.57.1 --restart` switches a server back. +- The request store's schema is unchanged (25): upgrading from 0.58.0 keeps the request history. A server upgrading from 0.57.x also gets 0.58.0's changes, so the 0.58.0 upgrade notes apply as well: the history is cleared once, and a configuration that still sets `security.hidden_text` or `security.output_limit` does not load. +- **A plugin's manifest has to be plain data.** + - The file declares it as `export const manifest = { … }`. A manifest exported any other way, such as `export { manifest }`, does not load. + - It may hold objects, arrays, strings, numbers, `true`, `false` and `null`. Keys are names or quoted strings. Trailing commas are fine, and so is a minus sign before a number. + - Expressions, function calls, references to variables, spreads, computed keys, getters and methods, and template literals with `${…}` are not data. Neither is a manifest that the module's code changes after declaring it. + - Such a plugin does not load. `gw.plugin.manifest_not_data_at` gives the line and column; `gw.plugin.manifest_not_data` is used when there is no position. +- **The manifest holds the plugin's configuration.** + - `on_error` (`"reject"`, the default, or `"skip"`) is a manifest field. + - `match` is the scope the plugin runs with. It used to be a suggestion that filled in the configured scope at install. + - Settings take `value` in place of `default`: it is the value the plugin gets. A setting still written with `default` does not load; rename the field to `value`. A setting without `value` gets its type's empty value (`""`, `0` or `false`). +- **Plugin entries in `config.yaml` hold only `id`, `file`, `sha256` and `enabled`.** + - Entries written by 0.58.0 may still have `on_error`, `scope` and `settings`. These are ignored whatever they contain, so the configuration still loads, and they are removed the next time core writes the `plugins` section. + - Their values are not carried over. To keep them, write them into the plugin's manifest, or set them again in the app. + - `rewrite` and `confirmed` are now reserved: a configuration with a plugin of either id does not load. +- **Plugins installed by 0.58.0.** + - The two default plugins are replaced with this release's versions on first start and take this release's setting values. One that was on is turned off, because this core cannot read what its 0.58.0 version asked for; turn it on again in the app. + - A plugin of your own whose manifest uses `default`, or is not plain data, does not load until its file is fixed and the change approved. While it is on, it refuses the requests it covers: `on_error` defaults to `reject`, and a 0.58.0 manifest cannot set it. +- Changed in the protocol: + - Removed: `PUT /plugins/{id}/source` (`ReplacePluginSource`, `PluginSourceReplace`), `UpdatePlugin`, `UpdatePluginConfirmed`, `PluginUpdate`, and `PluginView.settings` (the values are in `settings_schema[].value`). + - `PUT /plugins/{id}` is `SavePlugin` and `PUT /plugins/{id}/confirmed` is `SavePluginConfirmed`. Both take `PluginSave`: `source`, `enabled` and `base_version`. + - New: `POST /plugins/rewrite` (`PluginRewrite`: `PluginRewriteRequest` → `PluginSource`), `POST /plugins/confirmed` (`CreatePluginConfirmed`) and `POST /plugins/{id}/approve/confirmed` (`ApprovePluginFileConfirmed`). + - `PluginCreate` has only `source`, `id`, `enabled` and `base_version`. `SettingSpecView.default` is now `value`, and `ManifestView` has `on_error`. `PluginView.on_error` and `PluginView.scope` are what the plugin's file says. + - A web view may call `CreatePlugin`, `SavePlugin` and `ApprovePluginFile`. A client calls the three `…Confirmed` endpoints from native code after the user confirms, never from its web view. +- **Confirmations.** A plugin that can rewrite tool calls (the `reply.tool_calls` permission) needs the user's confirmation outside the web page to be installed, to be turned on, to have its code changed, or to have a change to its file approved. A plugin counts as one when its old or its new version has the permission, or when its old version cannot be read. + - The plain endpoints refuse these with 403 `control.plugin.needs_confirmation`. + - Nothing else needs a confirmation: changing only data, even of a plugin that can rewrite tool calls; turning a plugin off, deleting and reordering; and installing, turning on, changing or approving a plugin that cannot rewrite tool calls. In 0.58.0, every install, source replacement and file approval needed one, and so did changing the settings or scope of a plugin that can rewrite tool calls. + - `PUT /config`, `PATCH /config` and rollback refuse to add, turn on or re-approve a plugin that can rewrite tool calls, with the same 403. They are open to the web view and used to bypass the rule. They have no confirmed variant: those changes go through the plugin endpoints. +- Message codes, compared with 0.58.0: + - New: `gw.plugin.manifest_not_data` and `gw.plugin.manifest_not_data_at`. + - Removed: `config.plugin.blank_pattern` and `config.plugin.setting_type`. The configuration fields they checked are gone. + - `control.plugin.needs_confirmation` has a new sentence that names the four actions. Its argument (`plugin`) is unchanged. + +**Plugin files hold their configuration.** The app changes `on_error`, `match` or a setting by rewriting the plugin's source and saving it. `POST /plugins/rewrite` takes a source and the new values and returns the rewritten source; it reads and writes no file. `POST /plugins/inspect` reads the values back from a source. +- Only the manifest's bytes change. Every other byte of the file stays as it was. +- Core writes the manifest in one fixed style: two-space indent, one manifest field per line, and nested objects and lists on one line when they fit in 80 columns. Comments inside the manifest are not kept. +- In a rewrite request, `on_error` and `scope` are the values to end up with, and `settings` changes only the settings it names. Each scope entry is trimmed. A setting the plugin does not declare (`gw.plugin.setting_unknown`), a value of the wrong type (`gw.plugin.setting_type`) and an empty scope entry (`control.plugin.blank_pattern`) are refused with 400. +- Core finds the manifest with a small tokenizer that skips strings, template literals, comments and regular expressions, so text that looks like a manifest inside them is not taken for it. +- A plugin whose file changed, or that does not load, follows the `on_error` and `match` of its approved file. The manifest is data, so core reads them even when the plugin cannot run. + +**Saving a plugin.** `PUT /plugins/{id}` takes the whole source and whether the plugin is on. Core compares the source with the approved one: +- **Data-only:** the bytes outside the manifest are identical, and the manifest differs only in `on_error`, `match` and the settings' `value`. +- **Code change:** anything else, including a change to the name, description, permissions, request kinds, or a setting's type or label. +- **Same bytes:** only `enabled` changes, and the files are not touched. + +When a save writes the file, the plugin file, its approved copy and its `sha256` in `config.yaml` change together, with no moment in between when the file counts as changed: a plugin that is on keeps running across the save. A save is tied to the configuration version it read, and fails with 409 when the configuration has moved on. A file edited by hand still stops the plugin until the change is approved, even when only a value changed. + +**Default plugins.** `reply-language` and `wsl-paths` write `on_error` and their settings' `value` in their manifests, in the style core writes, so changing a setting in the app changes one line of the file. The record of what core offered follows data-only saves and approvals, so a default whose settings, scope or `on_error` were changed still counts as unchanged code. A later version replaces it and keeps what was written in the file: `on_error`, `match`, and the values of the settings it still declares with the same type. A default whose code was changed is still left alone, and a new version that asks for more permissions or request kinds still comes back turned off. + +**Windows.** `twcore.exe` no longer needs the Visual C++ Redistributable; the C runtime is linked statically. The `twcore.exe` of 0.58.0 imported `VCRUNTIME140.dll`, which comes with that package and not with Windows, so the gateway did not start on a machine without it.