ThinkWatch Core 0.59.0
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/<id>.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 --restartswitches 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_textorsecurity.output_limitdoes 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 asexport { manifest }, does not load. - It may hold objects, arrays, strings, numbers,
true,falseandnull. 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_atgives the line and column;gw.plugin.manifest_not_datais used when there is no position.
- The file declares it as
- The manifest holds the plugin's configuration.
on_error("reject", the default, or"skip") is a manifest field.matchis the scope the plugin runs with. It used to be a suggestion that filled in the configured scope at install.- Settings take
valuein place ofdefault: it is the value the plugin gets. A setting still written withdefaultdoes not load; rename the field tovalue. A setting withoutvaluegets its type's empty value ("",0orfalse).
- Plugin entries in
config.yamlhold onlyid,file,sha256andenabled.- Entries written by 0.58.0 may still have
on_error,scopeandsettings. These are ignored whatever they contain, so the configuration still loads, and they are removed the next time core writes thepluginssection. - Their values are not carried over. To keep them, write them into the plugin's manifest, or set them again in the app.
rewriteandconfirmedare now reserved: a configuration with a plugin of either id does not load.
- Entries written by 0.58.0 may still have
- 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_errordefaults toreject, and a 0.58.0 manifest cannot set it.
- Changed in the protocol:
- Removed:
PUT /plugins/{id}/source(ReplacePluginSource,PluginSourceReplace),UpdatePlugin,UpdatePluginConfirmed,PluginUpdate, andPluginView.settings(the values are insettings_schema[].value). PUT /plugins/{id}isSavePluginandPUT /plugins/{id}/confirmedisSavePluginConfirmed. Both takePluginSave:source,enabledandbase_version.- New:
POST /plugins/rewrite(PluginRewrite:PluginRewriteRequest→PluginSource),POST /plugins/confirmed(CreatePluginConfirmed) andPOST /plugins/{id}/approve/confirmed(ApprovePluginFileConfirmed). PluginCreatehas onlysource,id,enabledandbase_version.SettingSpecView.defaultis nowvalue, andManifestViewhason_error.PluginView.on_errorandPluginView.scopeare what the plugin's file says.- A web view may call
CreatePlugin,SavePluginandApprovePluginFile. A client calls the three…Confirmedendpoints from native code after the user confirms, never from its web view.
- Removed:
- Confirmations. A plugin that can rewrite tool calls (the
reply.tool_callspermission) 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 /configand 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.
- The plain endpoints refuse these with 403
- Message codes, compared with 0.58.0:
- New:
gw.plugin.manifest_not_dataandgw.plugin.manifest_not_data_at. - Removed:
config.plugin.blank_patternandconfig.plugin.setting_type. The configuration fields they checked are gone. control.plugin.needs_confirmationhas a new sentence that names the four actions. Its argument (plugin) is unchanged.
- New:
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_errorandscopeare the values to end up with, andsettingschanges 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_errorandmatchof 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,matchand 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
enabledchanges, 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.
Requests full of secrets. Outbound redaction no longer slows down when one request holds many distinct values that look like secrets: finding, replacing and restoring them, and hiding them from plugins, now take time in proportion to the request's size instead of growing with the square of the number of values. The security log records at most the first 100 distinct values of a request, and every value is still replaced.
Downloads
| Platform | Binary | Archive for server installation |
|---|---|---|
| Linux, x86_64 | twcore-x86_64-unknown-linux-gnu |
twcore-x86_64-unknown-linux-gnu.tar.gz |
| Linux, aarch64 | twcore-aarch64-unknown-linux-gnu |
twcore-aarch64-unknown-linux-gnu.tar.gz |
| macOS, Apple silicon | twcore-aarch64-apple-darwin |
— |
| Windows, x64 | twcore-x86_64-pc-windows-msvc.exe |
— |
| Windows, ARM64 | twcore-aarch64-pc-windows-msvc.exe |
— |
Each file is published with a .sha256 file beside it. A Linux archive contains twcore, the systemd unit twcore.service and LICENSE. ThinkWatch Lite includes its own copy of twcore; the files here are for running core separately, such as on a server.
Server installation
On Linux (x86_64 or aarch64), the install script sets up twcore as a systemd service. This installs 0.59.0:
curl -fsSL https://raw.githubusercontent.com/ThinkWatchProject/ThinkWatch-Core/main/scripts/install.sh | sudo sh -s -- --version 0.59.0An installation made with the script switches to 0.59.0 with:
sudo twcore upgrade --version 0.59.0 --restartConfiguration, the remote control port and connecting ThinkWatch Lite are described in docs/server.md.
Verifying a download
A .sha256 file holds the SHA-256 of the file followed by its name. With both files in the current directory, on Linux:
sha256sum -c twcore-x86_64-unknown-linux-gnu.tar.gz.sha256On macOS:
shasum -a 256 -c twcore-aarch64-apple-darwin.sha256On Windows, in PowerShell, the following prints True when the binary matches:
(Get-FileHash .\twcore-x86_64-pc-windows-msvc.exe).Hash -eq (Get-Content .\twcore-x86_64-pc-windows-msvc.exe.sha256).Split()[0]The install script and twcore upgrade check the SHA-256 themselves.
What's Changed
- Plugins keep their configuration in their own file (protocol 35) by @fylorn in #276
- Link the VC runtime statically into twcore.exe by @fylorn in #277
- chore: v0.59.0 by @fylorn in #278
- Linear-time redaction for requests full of secrets, a per-request report cap, and output-cap edge cases by @fylorn in #279
Full Changelog: v0.58.0...v0.59.0