From 917865c22531ccd192b2cda3b97909aeccb47f07 Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:00:04 +0800 Subject: [PATCH] 2026.10.28: conversations with encrypted history stay on their upstream; pin core v0.81.0 - Pin core v0.81.0 and regenerate the control-plane types (route continuity gains the reason "sealed"). - Translate gw.route.sealed_upstream_gone and gw.upstream.sealed_elsewhere. - The request details explain a request sent back to the upstream that encrypted content in its conversation. - Version 2026.10.28 and its release notes. Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- release-notes/2026.10.28.md | 11 +++++++++ src-tauri/Cargo.lock | 30 +++++++++++------------ src-tauri/Cargo.toml | 14 +++++------ src-tauri/tauri.conf.json | 2 +- src/generated/tw-api.ts | 2 +- src/i18n/core.zh.json | 2 ++ src/traffic/drawer/RequestDrawer.i18n.tsx | 2 ++ src/traffic/drawer/Routing.tsx | 1 + 9 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 release-notes/2026.10.28.md diff --git a/package.json b/package.json index f655a16a..3b774ca4 100644 --- a/package.json +++ b/package.json @@ -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.27", + "version": "2026.10.28", "type": "module", "packageManager": "pnpm@11.13.0", "scripts": { diff --git a/release-notes/2026.10.28.md b/release-notes/2026.10.28.md new file mode 100644 index 00000000..fa7d21ac --- /dev/null +++ b/release-notes/2026.10.28.md @@ -0,0 +1,11 @@ +**Upgrade notes:** +- The bundled core is now 0.81.0. The control-plane protocol stays at 51, so a remote server running core 0.80.0 still connects; the behavior described below needs 0.81.0 on the side that runs the gateway. +- Nothing is cleared; the request history, stored payloads and settings carry over. + +**Conversations with encrypted history stay on their upstream:** +- OpenAI Responses upstreams, such as a ChatGPT account, hand clients encrypted history that only the same account can read. Codex sends it back every turn. Before, moving Codex's route to another upstream made a conversation under way fail on every turn. +- Such a conversation now goes back to the upstream that encrypted its history, even after its route changes. The request details show this under Conversation. +- If that upstream was removed, or another upstream is asked to read the history, the request fails with a message that explains why and suggests routing the conversation back or starting a new one. + +**Routing:** +- In the route table's rule list, the letter tile shown for an upstream without a logo lines up with the text. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d04e3fb6..135edc35 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4833,7 +4833,7 @@ dependencies = [ [[package]] name = "thinkwatch-lite" -version = "2026.10.27" +version = "2026.10.28" dependencies = [ "anyhow", "base64 0.22.1", @@ -5338,8 +5338,8 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "serde", "serde_json", @@ -5350,8 +5350,8 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "memchr", "serde", @@ -5360,8 +5360,8 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "aho-corasick", "base64 0.22.1", @@ -5379,8 +5379,8 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "serde", "serde_json", @@ -5406,8 +5406,8 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "serde", "ts-rs", @@ -5415,8 +5415,8 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "libc", "notify", @@ -5426,8 +5426,8 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.80.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.80.0#c876eef8362711f99c81ddab52ae66c54b98ca90" +version = "0.81.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.81.0#aae0e486da837c94189046a7bcf5095d4eb1948f" dependencies = [ "saphyr-parser", "thiserror 2.0.21", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 30c66172..37011f06 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thinkwatch-lite" -version = "2026.10.27" +version = "2026.10.28" edition = "2024" # **这个数决定依赖能升到哪一版。**edition 2024 的解析器只挑声明的 Rust # 版本编得动的依赖:写 1.85 的时候,`cargo update` 一直停在旧的 time 和 @@ -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.80.0" } -tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.80.0" } -tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.80.0" } -tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.80.0" } -tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.80.0" } -tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.80.0" } +tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" } +tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" } +tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" } +tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" } +tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" } +tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" } [lib] name = "thinkwatch_lite_lib" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index a3b110f7..c878c0a7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ThinkWatch Lite", - "version": "2026.10.27", + "version": "2026.10.28", "identifier": "app.thinkwatch.lite", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/generated/tw-api.ts b/src/generated/tw-api.ts index 148047f2..3666424b 100644 --- a/src/generated/tw-api.ts +++ b/src/generated/tw-api.ts @@ -5487,7 +5487,7 @@ gateway_reachable: Array, }; /** * 留在上次回答这段对话的那一家的理由。 */ -export type Stay = "turn" | "cache"; +export type Stay = "turn" | "cache" | "sealed"; /** * 观测这一层在不在记。 diff --git a/src/i18n/core.zh.json b/src/i18n/core.zh.json index 203ef6ef..51e0f101 100644 --- a/src/i18n/core.zh.json +++ b/src/i18n/core.zh.json @@ -479,6 +479,7 @@ "gw.wait.mixed": "等待期间没有可处理此请求的上游恢复可用。因 429 暂停:{rate_limited!names}。达到按分钟或小时计的用量上限:{window_full!names}。当天、当周或当月的用量上限已用完:{used_up!names}。达到并发上限:{busy!names}。请稍后重试。", "gw.route.upstream_missing": "配置中不存在「{upstream}」。", "gw.route.selected_upstream_missing": "规则「{rule}」选中的上游「{upstream}」在配置中不存在。", + "gw.route.sealed_upstream_gone": "此对话的历史由上游「{upstream}」加密,该上游已不在配置中,其他上游无法读取。请开始新的对话。", "gw.route.all_selected_disabled": "路由选中的上游均已停用:{detail}", "gw.route.protocol_mismatch": "{path} 只能由 {wanted} 格式的上游处理,而上游「{upstream}」是 {got} 格式。", "gw.count_tokens.bedrock_upstream": "无法通过 AWS Bedrock 上游「{upstream}」计算 token 数。", @@ -498,6 +499,7 @@ "gw.upstream.forward_failed": "转发失败:{detail}", "gw.upstream.body_broken": "回答接收途中与上游的连接断开:{detail}", "gw.upstream.rate_limited": "上游「{upstream}」触发限流。", + "gw.upstream.sealed_elsewhere": "上游「{upstream}」无法读取此对话中加密的历史:历史由另一个上游加密,只有该上游能够读取。请将此对话改回原来的上游,或开始新的对话。上游返回:{message}", "gw.upstream.paused": "上游「{upstream}」返回 429(限流),已暂停发送,约 {secs} 秒后恢复。", "gw.upstream.window_full": "上游「{upstream}」已达到一条按分钟或小时计的用量上限,约 {secs} 秒后空出额度。", "gw.upstream.period_used_up": "上游「{upstream}」当天、当周或当月的一条用量上限已用完,将于 {resets} 重置。", diff --git a/src/traffic/drawer/RequestDrawer.i18n.tsx b/src/traffic/drawer/RequestDrawer.i18n.tsx index 13a824a1..84e4fa3d 100644 --- a/src/traffic/drawer/RequestDrawer.i18n.tsx +++ b/src/traffic/drawer/RequestDrawer.i18n.tsx @@ -103,6 +103,7 @@ export const requestDrawerText = messages( heldRoute: "沿用本轮开头的路由决定", stayedTurn: "同一轮内留在上次回答的上游", stayedCache: "上次回答的缓存仍有效,留在该上游", + stayedSealed: "对话带有该上游加密的内容,只能送回该上游", /** 选定上游之后才判断、拒绝了此请求的规则 */ deniedBy: "拒绝规则", /** 规则写的拒绝理由,或者选中的上游为何都无法服务 */ @@ -275,6 +276,7 @@ export const requestDrawerText = messages( heldRoute: "Kept the route decided at the start of this turn", stayedTurn: "Stayed with the upstream that answered earlier in this turn", stayedCache: "Stayed with the upstream that answered last, while its cache is warm", + stayedSealed: "Sent back to the upstream that encrypted content in this conversation", deniedBy: "Denied by", reason: "Reason", attempts: "Attempts", diff --git a/src/traffic/drawer/Routing.tsx b/src/traffic/drawer/Routing.tsx index be29b328..e4e81127 100644 --- a/src/traffic/drawer/Routing.tsx +++ b/src/traffic/drawer/Routing.tsx @@ -70,6 +70,7 @@ export function Routing({ r, plugins, running }: { r: HistoryRow; plugins: Plugi {f.continuity.heldRoute && {t.heldRoute}} {f.continuity.stayed === "turn" && {t.stayedTurn}} {f.continuity.stayed === "cache" && {t.stayedCache}} + {f.continuity.stayed === "sealed" && {t.stayedSealed}} } />