Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "thinkwatch-lite",
"private": true,
"description": "Desktop app for a local AI API gateway on macOS, Windows and Linux",
"version": "2026.10.24",
"version": "2026.10.25",
"type": "module",
"packageManager": "pnpm@11.13.0",
"scripts": {
Expand Down
27 changes: 27 additions & 0 deletions release-notes/2026.10.25.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
**Upgrade notes:**
- The bundled core is now 0.78.0 and the control-plane protocol is 50. A remote server running an older core must be upgraded to 0.78.0 to stay connected.
- Nothing is cleared; the request history, stored payloads and settings carry over.
- Backups replace the version history. The old history entries are removed when the first backup is kept.
- An upstream address that contains a backslash, or a user name or password before the host, is no longer accepted. A configuration with one opens in safe mode; put the credential in the key or a header instead.

**Sync and backup (Settings > Sync and backup):**
- Export the whole configuration, or chosen items, to a `.twconfig` file. Items they depend on come along. Keys are included by default and can be left out; the file can be encrypted with a passphrase.
- Import adds what is new. An item with the same name and different content is a conflict, and each conflict is overwritten, imported under a new name, or skipped. Imported plugins start disabled, and ChatGPT and other OAuth logins are signed in again on each device. Packs from older releases are repaired, and the preview lists every fix.
- Every configuration change keeps a backup, plugins included, and the last 10 are kept. Restore a whole version or only the items you need, such as one deleted upstream. A restore can be undone. Backups can also be restored from safe mode.
- Sync keeps one configuration across your Macs, PCs and remote cores through a folder (iCloud Drive, Dropbox, OneDrive, Syncthing, a network share), a WebDAV server or S3-compatible storage. Changes made on different devices merge item by item; only the same setting changed two ways is a conflict, and it is resolved once on any device. Encryption with a sync passphrase is on by default.
- Upstreams can be kept on this device only, together with the routes and aliases that use them. Gateway keys used by clients this device has taken over are never removed by sync, and clients are updated when a key changes.
- The sync location also keeps the latest and the previous version as an off-site backup.

**Upstream sharing:**
- Share... in an upstream's menu creates an import link, with the key included by default and a warning that anyone with the link can use it. Select upstreams to export them as a file.
- A new installation offers Import config... and Join sync... on the empty Upstreams page.

**Security log:**
- Filter the log by type from the Type column header. Paging, per-day counts and export follow the filter.

**Menu bar:**
- The speed line shows 0 t/s instead of a dash when nothing is generating.
- The sweeping glow runs on the TW mark only. In the numbers-only style, a dot to the left of the numbers breathes while requests run.

**Tool-call checks:**
- Streamed tool calls are no longer judged on arguments that are still arriving, so a host cut off mid-stream is not mistaken for an external one. Hits that are only recorded are logged with the whole call. `-T` counts as uploading a file only inside a curl command.
21 changes: 14 additions & 7 deletions scripts/shots/core/en/overview.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@
"kind": "group"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "x-api-key",
Expand Down Expand Up @@ -211,7 +212,8 @@
"kind": "group"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "authorization",
Expand Down Expand Up @@ -250,7 +252,8 @@
"rule": "catch-all"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "authorization",
Expand Down Expand Up @@ -284,7 +287,8 @@
"rule": "qwen"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "x-api-key",
Expand Down Expand Up @@ -314,7 +318,8 @@
"rule": "deepseek"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "x-goog-api-key",
Expand Down Expand Up @@ -349,7 +354,8 @@
"rule": "gemini"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "authorization",
Expand Down Expand Up @@ -380,7 +386,8 @@
"rule": "local-models"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
}
],
"proxies": [
Expand Down
21 changes: 14 additions & 7 deletions scripts/shots/core/zh/overview.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@
"kind": "group"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "x-api-key",
Expand Down Expand Up @@ -211,7 +212,8 @@
"kind": "group"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "authorization",
Expand Down Expand Up @@ -250,7 +252,8 @@
"rule": "兜底"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "authorization",
Expand Down Expand Up @@ -284,7 +287,8 @@
"rule": "Qwen 模型"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "x-api-key",
Expand Down Expand Up @@ -314,7 +318,8 @@
"rule": "DeepSeek 模型"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "x-goog-api-key",
Expand Down Expand Up @@ -349,7 +354,8 @@
"rule": "Gemini 模型"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
},
{
"auth_header": "authorization",
Expand Down Expand Up @@ -380,7 +386,8 @@
"rule": "本地模型"
}
],
"signed_in": null
"signed_in": null,
"sync_excluded": false
}
],
"proxies": [
Expand Down
50 changes: 48 additions & 2 deletions scripts/shots/mock/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,31 @@ export const CORE: { [N in WebviewEndpoint]: Handler<N> } = {
GetConfig: () => ({ path: "~/.thinkwatch/config.yaml", text: CONFIG_TEXT, version: configVersion() }),
PatchConfig: refuse,
PutConfig: refuse,
ConfigHistory: () => [{ version: configVersion(), at_ms: NOW - 2 * DAY, origin: "ui", bytes: 2_714, current: true }],
ConfigAt: () => ({ section: null, name: null }),
ConfigRollback: refuse,
ConfigRepairPlan: () => ({ base_version: configVersion(), fixes: [] }),
RepairConfig: refuse,
SaveListen: refuse,
// 导入导出与备份:只读的列一份当前版本,写的拒绝
ConfigItems: () => overview(lastSeen()).providers.map((p) => ({ item: { kind: "provider", name: p.name }, refs: [] })),
ExportConfig: refuse,
ImportPreview: refuse,
ImportConfig: refuse,
Backups: () => [
{
id: "0000000001-00000000",
at_ms: NOW - 2 * DAY,
origin: "ui",
device: null,
local: true,
sync: false,
counts: { providers: overview(lastSeen()).providers.length, plugins: 0 },
core_version: "0.77.0",
current: true,
},
],
BackupFile: refuse,
RestorePreview: refuse,
Restore: refuse,

History: (req) => {
const from = req.from_ms ?? -Infinity;
Expand Down Expand Up @@ -228,6 +247,33 @@ export const CORE: { [N in WebviewEndpoint]: Handler<N> } = {
ReorderPlugins: refuse,
TrialPlugin: refuse,
PluginLogs: () => [],
// 同步(协议 50):截图里没开同步,改的一律拒绝
SyncStatus: () => ({
state: "off",
target: null,
encrypted: true,
device: null,
last_synced_ms: null,
failing_since_ms: null,
error: null,
devices: [],
conflicts: 0,
skipped: 0,
notices: [],
excluded: [],
excluded_cascade: [],
}),
SyncTest: refuse,
SyncJoinPreview: refuse,
SyncEnable: refuse,
SyncSave: refuse,
SyncNow: refuse,
SyncConflicts: () => [],
SyncResolve: refuse,
SyncEncryption: refuse,
SyncUnlock: refuse,
SyncDisable: refuse,
SyncRepublish: refuse,
};

/** ChatGPT Plus 的两个额度窗口:5 小时用了一半多,每周的三成 */
Expand Down
31 changes: 16 additions & 15 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "thinkwatch-lite"
version = "2026.10.24"
version = "2026.10.25"
edition = "2024"
# **这个数决定依赖能升到哪一版。**edition 2024 的解析器只挑声明的 Rust
# 版本编得动的依赖:写 1.85 的时候,`cargo update` 一直停在旧的 time 和
Expand All @@ -26,12 +26,12 @@ license = "MIT"
# twcore 二进制必须和这里编译进去的协议镜像来自同一个 core 版本 —— 打包脚本正是
# 从 tw-api 锁到的 tag 去取二进制的(见 scripts/fetch-core.sh)。
[workspace.dependencies]
tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" }
tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" }
tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" }
tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" }
tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" }
tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.78.0" }
tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.78.0" }
tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.78.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.78.0" }
tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.78.0" }
tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.78.0" }

[lib]
name = "thinkwatch_lite_lib"
Expand Down Expand Up @@ -70,6 +70,8 @@ tw-guard = { workspace = true }
tw-link = { workspace = true }

anyhow = "1"
# 配置包(`.twconfig`)在网页和这一侧之间按 Base64 交接(`export`)。锁里本来就有的那一版
base64 = "0.22"
# 新连接的 id、诊断包的临时目录名
rand = "0.10"
libc = "0.2"
Expand Down
Loading
Loading