From 5386162006531686cd05d6aabf3447db06d3e414 Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:46:06 +0800 Subject: [PATCH 1/2] feat(security): redaction rules match text as written or by regex, with a case switch A custom redaction rule now has the same "match by" choice as content rules, minus code points: Contains (the text as written, the default) or Regex. Next to it, a "Match case" toggle; off by default. The pattern hint, the live test, saving, the rule list and the security-log detail all follow it. Needs core with control API 47; the generated types already describe it, the pin moves when core is tagged. Co-Authored-By: Claude Opus 5.5 --- src/generated/tw-api.ts | 39 +++++++++++++---- src/i18n/core.zh.json | 2 + src/security/HitDetail.tsx | 1 + src/security/RuleDialog.i18n.ts | 22 +++++++--- src/security/RuleDialog.tsx | 77 +++++++++++++++++++++++---------- src/security/api.ts | 16 +++++-- src/security/labels.i18n.tsx | 29 +++++++++++-- src/security/labels.tsx | 4 +- src/security/useTrial.ts | 12 ++--- 9 files changed, 149 insertions(+), 53 deletions(-) diff --git a/src/generated/tw-api.ts b/src/generated/tw-api.ts index 6fb060f9..147c1cdb 100644 --- a/src/generated/tw-api.ts +++ b/src/generated/tw-api.ts @@ -1,6 +1,6 @@ // Generated by tw-api (`tw_api::ts::export_all`). Do not edit by hand. -export const CONTROL_API_VERSION = 46; +export const CONTROL_API_VERSION = 47; /** * 手动中止(`POST /request/{id}/abort`、`POST /sessions/{id}/abort`)叫停了哪几个请求。 @@ -1056,7 +1056,8 @@ no_usage_requests: number, }; */ export type CustomRuleSave = { name: string, /** - * 正则;内容过滤按 `match`:要找的那段文字、正则,或者码位(`U+200B, U+E0000–U+E007F`) + * 工具调用审查是正则;出站脱敏和内容过滤按 `match`:要找的那段文字、正则,或者码位 + * (`U+200B, U+E0000–U+E007F`,只有内容过滤) */ pattern: string, /** @@ -1064,10 +1065,14 @@ pattern: string, */ action?: RuleAction | null, /** - * 内容过滤才有:`contains`(不分大小写的子串)/ `regex` / `codepoints`。不给按 - * `contains`。别的防护的自定义规则都是正则 + * 怎么认。内容过滤:`contains`(不分大小写的子串)/ `regex` / `codepoints`;出站脱敏: + * `contains`(按原样找这段文字)/ `regex`。不给按 `contains`。工具调用审查的都是正则 */ match?: ContentMatch | null, +/** + * 出站脱敏才有:分大小写。不给是不分 + */ +case_sensitive?: boolean | null, /** * 出站脱敏才有:占位符名称,`PROJECT` 换成 `<>`。大写字母开头,其余是 * 大写字母、数字、下划线,最多 24 个字符。不给是 `SECRET` @@ -2586,7 +2591,15 @@ base_version?: string | null, }; /** * 一条规则按什么认。**给界面说明用**,界面按类型写成自己的话。 */ -export type Matcher = { "kind": "prefix", prefix: string, min_tail: number, } | { "kind": "openai-legacy", min_len: number, } | { "kind": "pem" } | { "kind": "jwt" } | { "kind": "conn-string" } | { "kind": "private-ip" } | { "kind": "domain-suffix", suffixes: Array, } | { "kind": "cn-resident-id", born_since: number, } | { "kind": "bank-card", networks: Array, } | { "kind": "email" } | { "kind": "cn-mobile-phone" } | { "kind": "regex", pattern: string, } | { "kind": "contains", text: string, } | { "kind": "codepoints", ranges: Array, } | { "kind": "builtin", check: string, }; +export type Matcher = { "kind": "prefix", prefix: string, min_tail: number, } | { "kind": "openai-legacy", min_len: number, } | { "kind": "pem" } | { "kind": "jwt" } | { "kind": "conn-string" } | { "kind": "private-ip" } | { "kind": "domain-suffix", suffixes: Array, } | { "kind": "cn-resident-id", born_since: number, } | { "kind": "bank-card", networks: Array, } | { "kind": "email" } | { "kind": "cn-mobile-phone" } | { "kind": "regex", pattern: string, +/** + * 分大小写:工具调用审查的规则、写明了要分的出站脱敏自定义规则。内容过滤的不分 + */ +case_sensitive?: boolean, } | { "kind": "contains", text: string, +/** + * 分大小写:写明了要分的出站脱敏自定义规则。内容过滤的不分 + */ +case_sensitive?: boolean, } | { "kind": "codepoints", ranges: Array, } | { "kind": "builtin", check: string, }; /** * 中位数和样本数。 @@ -4292,9 +4305,14 @@ name: string, */ pattern?: string | null, /** - * 内容过滤:这条规则怎么认(`contains` / `regex` / `codepoints`)。别的防护没有 + * 自定义规则怎么认:内容过滤是 `contains` / `regex` / `codepoints`,出站脱敏是 + * `contains` / `regex`。内置规则和工具调用审查没有 */ matching?: ContentMatch | null, +/** + * 出站脱敏的自定义规则:分大小写 + */ +case_sensitive?: boolean, /** * 认出它的 core 的版本 */ @@ -4739,13 +4757,18 @@ action?: RuleAction | null, }; */ export type SecurityTestRequest = { sample: string, /** - * 只试这一条(正在编辑的规则):出站脱敏和工具调用审查是正则,内容过滤按 `match` + * 只试这一条(正在编辑的规则):工具调用审查是正则,出站脱敏和内容过滤按 `match` */ pattern?: string | null, /** - * 内容过滤试 `pattern` 时怎么认:`contains` / `regex` / `codepoints`,不给按 `contains` + * 试 `pattern` 时怎么认:内容过滤是 `contains` / `regex` / `codepoints`,出站脱敏是 + * `contains` / `regex`。不给按 `contains` */ match?: ContentMatch | null, +/** + * 出站脱敏试 `pattern` 时分不分大小写。不给是不分 + */ +case_sensitive?: boolean | null, /** * 只试这一条内置规则(停用着的也能试) */ diff --git a/src/i18n/core.zh.json b/src/i18n/core.zh.json index 7c21e731..56046cd8 100644 --- a/src/i18n/core.zh.json +++ b/src/i18n/core.zh.json @@ -497,6 +497,7 @@ "security.bad_content_pattern": "匹配内容无法使用:{detail}", "security.bad_codepoints": "码位写法有误:{detail}", "security.bad_label": "占位符名称「{label}」不可用:须以大写字母开头,由 1 到 24 个大写字母、数字或下划线组成。", + "security.redact_match_bad": "出站脱敏规则只能按「包含」或「正则」匹配,不支持「{matching}」。", "security.pattern_empty": "匹配内容为空。", "security.no_action_of_its_own": "{guard:guard}的规则不单独设处置,命中后的处理由档位决定。", "control.session_not_found": "未找到会话 {id}。", @@ -683,6 +684,7 @@ "config.rule_pattern_bad": "自定义{what:rule_line}规则「{name}」的{what:pattern_of}有误:{detail}", "config.rule_codepoints_bad": "自定义内容过滤规则「{name}」的码位写法有误:{detail}", "config.rule_label_bad": "自定义出站脱敏规则「{name}」的占位符名称为「{label}」,须以大写字母开头,由 1 到 24 个大写字母、数字或下划线组成。", + "config.rule_match_bad": "自定义出站脱敏规则「{name}」的匹配方式为「{matching}」,出站脱敏规则只能为 contains 或 regex。", "config.unknown_rule": "security.{guard} 中的「{rule}」不是内置规则。", "config.failover_range": "failover.{field} 为 {value},须在 {min} 到 {max} 之间。", "config.plugin.bad_id": "插件 ID「{plugin}」写法有误:只能使用小写字母、数字和连字符,1 到 {max} 个字符。", diff --git a/src/security/HitDetail.tsx b/src/security/HitDetail.tsx index aaa8d582..faefa763 100644 --- a/src/security/HitDetail.tsx +++ b/src/security/HitDetail.tsx @@ -199,6 +199,7 @@ function RuleText({ e }: { e: SecurityEventView }) { <> {lt.custom} {mode && · {lt.matching[mode]}} + {s.case_sensitive && · {lt.caseSensitive}} )} diff --git a/src/security/RuleDialog.i18n.ts b/src/security/RuleDialog.i18n.ts index 5a328018..caaebd17 100644 --- a/src/security/RuleDialog.i18n.ts +++ b/src/security/RuleDialog.i18n.ts @@ -36,10 +36,14 @@ export const ruleDialogText = messages( regex: "匹配(正则表达式)", codepoints: "码位", }, - patternHint: { - redact: "匹配到的整段内容按凭据处理。", - inspect_tools: "按工具调用的参数匹配。", - }, + toolHint: "按工具调用的参数匹配。", + /** 出站脱敏的自定义规则:怎么找、分不分大小写 */ + redactHint: { + contains: (cased: boolean) => + `在整个请求中按原样查找这段文字,. * 等字符没有特殊含义;${cased ? "区分" : "不区分"}大小写。找到的内容按凭据替换。`, + regex: (cased: boolean) => `按正则表达式匹配,${cased ? "区分" : "不区分"}大小写。匹配到的整段内容按凭据替换。`, + }, + caseSensitive: "区分大小写", contentHint: { contains: "在用户消息和工具结果中查找这段文字,不区分大小写,首尾空格也参与匹配。", regex: "在用户消息和工具结果中匹配,不区分大小写。", @@ -146,10 +150,14 @@ export const ruleDialogText = messages( regex: "Match (regular expression)", codepoints: "Code points", }, - patternHint: { - redact: "Everything the pattern matches is treated as a credential.", - inspect_tools: "Matched against the arguments of each tool call.", + toolHint: "Matched against the arguments of each tool call.", + redactHint: { + contains: (cased: boolean) => + `Looked for as written anywhere in the request; characters such as . and * have no special meaning. ${cased ? "Case must match" : "Case is ignored"}. What is found is replaced like a credential.`, + regex: (cased: boolean) => + `A regular expression; ${cased ? "case must match" : "case is ignored"}. Everything it matches is replaced like a credential.`, }, + caseSensitive: "Match case", contentHint: { contains: "Searched for in user messages and tool results, ignoring case. Leading and trailing spaces count.", diff --git a/src/security/RuleDialog.tsx b/src/security/RuleDialog.tsx index a7902a9e..7a76c120 100644 --- a/src/security/RuleDialog.tsx +++ b/src/security/RuleDialog.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { CopyIcon } from "lucide-react"; +import { CaseSensitiveIcon, CopyIcon } from "lucide-react"; import { AlertDialog, AlertDialogCancel, @@ -27,6 +27,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { Segmented } from "@/ui/segmented"; import { StatusLabel } from "@/ui/status-dot"; import { Textarea } from "@/ui/textarea"; +import { Toggle } from "@/ui/toggle"; import { cn } from "@/lib/utils"; import { useText } from "@/i18n"; import { commonText } from "@/i18n/common.i18n"; @@ -46,23 +47,26 @@ const ACTIONS: Record = { content: ["block", "strip", "record"], }; -/** 内容规则的三种写法 */ -const MATCHES: readonly ContentMatch[] = ["contains", "regex", "codepoints"]; +/** 自定义规则能选的写法:内容规则三种,出站脱敏不认码位。工具调用审查只有正则,不选 */ +const MATCHES: Record<"redact" | "content", readonly ContentMatch[]> = { + redact: ["contains", "regex"], + content: ["contains", "regex", "codepoints"], +}; /** - * 一条规则写的是什么,以及怎么认。码位写成一行,和输入框里的写法一样。 + * 一条规则写的是什么,以及怎么认、分不分大小写。码位写成一行,和输入框里的写法一样。 * * **写不出来的是 `null`**:出站脱敏那几种(前缀、PEM、身份证号…)和代码里做的检查 * (`builtin`)没有一条能填进自定义规则的写法,所以也没有「复制为自定义规则」。 */ -export function patternOf(r: SecurityRuleView): { pattern: string; match: ContentMatch } | null { +export function patternOf(r: SecurityRuleView): { pattern: string; match: ContentMatch; cased: boolean } | null { switch (r.matcher.kind) { case "regex": - return { pattern: r.matcher.pattern, match: "regex" }; + return { pattern: r.matcher.pattern, match: "regex", cased: r.matcher.case_sensitive ?? false }; case "contains": - return { pattern: r.matcher.text, match: "contains" }; + return { pattern: r.matcher.text, match: "contains", cased: r.matcher.case_sensitive ?? false }; case "codepoints": - return { pattern: r.matcher.ranges.join(", "), match: "codepoints" }; + return { pattern: r.matcher.ranges.join(", "), match: "codepoints", cased: false }; default: return null; } @@ -250,8 +254,11 @@ export function RuleDialog({ const redact = guard === "redact"; const [name, setName] = useState(editing?.id ?? seed?.name ?? ""); const [pattern, setPattern] = useState(was?.pattern ?? seed?.pattern ?? ""); - // 只有内容过滤能选;别的两项的自定义规则都是正则 - const [match, setMatch] = useState(content ? (was?.match ?? seed?.match ?? "contains") : "regex"); + // 内容过滤和出站脱敏能选,默认按原样找;工具调用审查的自定义规则都是正则 + const choosable = content ? "content" : redact ? "redact" : null; + const [match, setMatch] = useState(choosable ? (was?.match ?? seed?.match ?? "contains") : "regex"); + // 出站脱敏:分不分大小写,默认不分 + const [cased, setCased] = useState(was?.cased ?? false); // 新建的规则默认是最重的那一种:专门写一条规则,多半就是要拦它 const acts = hasAction(guard) ? guard : null; const [action, setAction] = useState( @@ -273,7 +280,8 @@ export function RuleDialog({ sample, { pattern, - match: content ? match : undefined, + match: choosable ? match : undefined, + case_sensitive: redact ? cased : undefined, label: redact && labelValid ? label : undefined, action: acts ? action : undefined, }, @@ -300,7 +308,8 @@ export function RuleDialog({ name: name.trim(), pattern, action: acts ? action : undefined, - match: content ? match : undefined, + match: choosable ? match : undefined, + case_sensitive: redact ? cased : undefined, label: redact ? label : undefined, enabled: editing?.enabled ?? true, }); @@ -331,24 +340,46 @@ export function RuleDialog({ /> - {content && ( + {choosable && ( - - label={t.matchKind} - value={match} - options={MATCHES.map((m) => ({ id: m, label: t.matchKinds[m] }))} - onChange={(m) => { - setError(null); - setMatch(m); - }} - /> +
+ + label={t.matchKind} + value={match} + options={MATCHES[choosable].map((m) => ({ id: m, label: t.matchKinds[m] }))} + onChange={(m) => { + setError(null); + setMatch(m); + }} + /> + {redact && ( + { + setError(null); + setCased(on); + }} + > + + {t.caseSensitive} + + )} +
)} ; @@ -45,14 +55,14 @@ export const api = { deleteRule: (guard: Guard, name: string, baseVersion: string) => call("DeleteCustomRule", { base_version: baseVersion }, guard, name), /** - * 拿一段文本试一试。给了 `pattern` 就只试这一条(内容过滤按 `match` 认,出站脱敏按 - * `label` 写占位符),给了 `rule` 就只试这一条内置规则(停用着的也能试),都不给就按 + * 拿一段文本试一试。给了 `pattern` 就只试这一条(按 `match` 认,出站脱敏按 + * `case_sensitive` 分不分大小写、按 `label` 写占位符),给了 `rule` 就只试这一条内置规则(停用着的也能试),都不给就按 * 现在启用的全部规则。`action` 是对话框里选着、还没保存的处置:发出去的样子和会不会 * 被拒按它算 */ test: ( guard: Guard, sample: string, - only: { pattern?: string; match?: ContentMatch; rule?: string; label?: string; action?: RuleAction } = {}, + only: TrialOnly = {}, ) => call("TestSecurity", { sample, ...only }, guard), }; diff --git a/src/security/labels.i18n.tsx b/src/security/labels.i18n.tsx index e8ef7b6d..9caffce9 100644 --- a/src/security/labels.i18n.tsx +++ b/src/security/labels.i18n.tsx @@ -99,6 +99,7 @@ export const securityLabelsText = messages( regex: "正则", codepoints: "码位", }, + caseSensitive: "区分大小写", /** 日志按天分组时,一天的标题 */ day: { today: "今天", @@ -163,8 +164,18 @@ export const securityLabelsText = messages( 中国大陆手机号:{code("1")} 开头的 11 位数字,第二位为 3 到 9,前后不紧挨其他数字 ), - regex: (code: Code, pattern: string) => <>正则 {code(pattern)}, - contains: (code: Code, text: string) => <>包含 {code(text)},不区分大小写, + // 正则一般区分大小写,不区分的才说;包含两种都说 + regex: (code: Code, pattern: string, cased: boolean) => ( + <> + 正则 {code(pattern)} + {!cased && ",不区分大小写"} + + ), + contains: (code: Code, text: string, cased: boolean) => ( + <> + 包含 {code(text)},{cased ? "区分大小写" : "不区分大小写"} + + ), codepoints: (code: Code, ranges: string[]) => <>码位 {or(ranges.map(code), "、", "、")}, }, }, @@ -240,6 +251,7 @@ export const securityLabelsText = messages( regex: "Regex", codepoints: "Code points", }, + caseSensitive: "Matching case", day: { today: "Today", yesterday: "Yesterday", @@ -341,8 +353,17 @@ export const securityLabelsText = messages( Chinese mainland mobile numbers: 11 digits starting with {code("1")}, the second 3 to 9, not run together with other digits ), - regex: (code: Code, pattern: string) => <>Regex {code(pattern)}, - contains: (code: Code, text: string) => <>Contains {code(text)}, ignoring case, + regex: (code: Code, pattern: string, cased: boolean) => ( + <> + Regex {code(pattern)} + {!cased && ", ignoring case"} + + ), + contains: (code: Code, text: string, cased: boolean) => ( + <> + Contains {code(text)}, {cased ? "matching case" : "ignoring case"} + + ), codepoints: (code: Code, ranges: string[]) => <>Code points {or(ranges.map(code), ", ", " and ")}, }, }, diff --git a/src/security/labels.tsx b/src/security/labels.tsx index ece297a4..3bc4d6e8 100644 --- a/src/security/labels.tsx +++ b/src/security/labels.tsx @@ -219,9 +219,9 @@ export function MatcherText({ m }: { m: Matcher }) { case "builtin": return <>{t.builtin[m.check] ?? t.builtinOther}; case "regex": - return t.regex(code, m.pattern); + return t.regex(code, m.pattern, m.case_sensitive ?? false); case "contains": - return t.contains(code, m.text); + return t.contains(code, m.text, m.case_sensitive ?? false); case "codepoints": return t.codepoints(code, m.ranges); } diff --git a/src/security/useTrial.ts b/src/security/useTrial.ts index 33e1b471..53d82113 100644 --- a/src/security/useTrial.ts +++ b/src/security/useTrial.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { errorText } from "@/i18n/core.i18n"; -import type { ContentMatch, Guard, RuleAction, SecurityTestResult } from "@/types"; -import { api } from "./api"; +import type { Guard, SecurityTestResult } from "@/types"; +import { api, type TrialOnly } from "./api"; export type Trial = | { state: "idle" } @@ -21,12 +21,12 @@ export type Trial = export function useTrial( guard: Guard, sample: string, - only: { pattern?: string; match?: ContentMatch; rule?: string; label?: string; action?: RuleAction }, + only: TrialOnly, /** 为 false 时不试(比如正则还是空的、码位写错了) */ ready = true, ): Trial { const [trial, setTrial] = useState({ state: "idle" }); - const { pattern, match, rule, label, action } = only; + const { pattern, match, case_sensitive, rule, label, action } = only; useEffect(() => { if (!ready || sample.length === 0) { setTrial({ state: "idle" }); @@ -36,7 +36,7 @@ export function useTrial( setTrial((t) => (t.state === "done" ? t : { state: "running" })); const h = setTimeout(() => { api - .test(guard, sample, { pattern, match, rule, label, action }) + .test(guard, sample, { pattern, match, case_sensitive, rule, label, action }) .then((r) => alive && setTrial({ state: "done", ...r })) .catch((e) => alive && setTrial({ state: "failed", error: errorText(e) })); }, 250); @@ -44,6 +44,6 @@ export function useTrial( alive = false; clearTimeout(h); }; - }, [guard, sample, pattern, match, rule, label, action, ready]); + }, [guard, sample, pattern, match, case_sensitive, rule, label, action, ready]); return trial; } From 816e6d048fc3ecc8fcfeb396b41f606c1b5ac0dd Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:35:45 +0800 Subject: [PATCH 2/2] 2026.10.17: pin core v0.71.0; release notes and version Core 0.71.0 (protocol 47): redaction keeps away from field names and, for custom rules, from protocol identifiers; the private-key rule stays within one text; custom redaction rules match text as written or by regex, with a case switch. Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- release-notes/2026.10.17.md | 9 +++++++++ src-tauri/Cargo.lock | 30 +++++++++++++++--------------- src-tauri/Cargo.toml | 14 +++++++------- src-tauri/tauri.conf.json | 2 +- src/generated/tw-api.ts | 3 ++- 6 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 release-notes/2026.10.17.md diff --git a/package.json b/package.json index b0186fcd..59cd1730 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.16", + "version": "2026.10.17", "type": "module", "packageManager": "pnpm@11.13.0", "scripts": { diff --git a/release-notes/2026.10.17.md b/release-notes/2026.10.17.md new file mode 100644 index 00000000..3d206329 --- /dev/null +++ b/release-notes/2026.10.17.md @@ -0,0 +1,9 @@ +**Upgrade notes:** +- The bundled core is now 0.71.0, with control-plane protocol 47. A remote server must run core 0.71.0; servers on 0.70.x do not connect until they are upgraded. +- Nothing is cleared; the request history, stored payloads and settings carry over. +- **Check your custom outbound redaction rules.** A rule written before this release was a case-sensitive regular expression; it now looks for its text as written and ignores case. A rule that relies on a regular expression keeps loading and simply stops matching, with nothing reported, so what it used to replace is sent as it is. Open each such rule and choose **Regex** under **Match by**, and **Match case** to keep telling capital letters from small ones. + +**Outbound redaction:** +- Field names in a request are no longer searched, and custom rules leave alone the identifiers a protocol needs exactly as they are: item and call ids, types, roles, tool names and model names. A custom rule whose text appeared inside an id that Codex sends with its history used to replace part of the id; the upstream then rejected that request and every later turn of the conversation. Built-in rules still search those values. +- The private-key rule now looks for the end of a key block only within the same text. A key header without an end in one message no longer joins up with an end line in a later message, which used to replace everything in between with one placeholder. +- A custom redaction rule has **Match by**: **Contains** looks for the text exactly as written, so `.` and `*` have no special meaning; **Regex** takes a regular expression. **Match case** next to it tells capital letters from small ones; it is off by default. The test box tries the chosen settings, and the rule list and the security log detail say how each rule matches. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index db05cbf9..f93aebbb 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4832,7 +4832,7 @@ dependencies = [ [[package]] name = "thinkwatch-lite" -version = "2026.10.16" +version = "2026.10.17" dependencies = [ "anyhow", "block2", @@ -5335,8 +5335,8 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "serde", "serde_json", @@ -5347,8 +5347,8 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "memchr", "serde", @@ -5357,8 +5357,8 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "base64 0.22.1", "bytes", @@ -5374,8 +5374,8 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "serde", "serde_json", @@ -5401,8 +5401,8 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "serde", "ts-rs", @@ -5410,8 +5410,8 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "libc", "notify", @@ -5421,8 +5421,8 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.70.1" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.70.1#5be17492a4a54443b0267ddc4b3d1892c730a041" +version = "0.71.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.71.0#911f9373bdbc4e39c37f25408aa802f3c4a32028" dependencies = [ "saphyr-parser", "thiserror 2.0.21", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 01ee30ec..244087b8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thinkwatch-lite" -version = "2026.10.16" +version = "2026.10.17" 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.70.1" } -tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.70.1" } -tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.70.1" } -tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.70.1" } -tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.70.1" } -tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.70.1" } +tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.71.0" } +tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.71.0" } +tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.71.0" } +tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.71.0" } +tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.71.0" } +tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.71.0" } [lib] name = "thinkwatch_lite_lib" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 24c910f0..fadd83fc 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.16", + "version": "2026.10.17", "identifier": "app.thinkwatch.lite", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/generated/tw-api.ts b/src/generated/tw-api.ts index 147c1cdb..2dcfeb09 100644 --- a/src/generated/tw-api.ts +++ b/src/generated/tw-api.ts @@ -2593,7 +2593,8 @@ base_version?: string | null, }; */ export type Matcher = { "kind": "prefix", prefix: string, min_tail: number, } | { "kind": "openai-legacy", min_len: number, } | { "kind": "pem" } | { "kind": "jwt" } | { "kind": "conn-string" } | { "kind": "private-ip" } | { "kind": "domain-suffix", suffixes: Array, } | { "kind": "cn-resident-id", born_since: number, } | { "kind": "bank-card", networks: Array, } | { "kind": "email" } | { "kind": "cn-mobile-phone" } | { "kind": "regex", pattern: string, /** - * 分大小写:工具调用审查的规则、写明了要分的出站脱敏自定义规则。内容过滤的不分 + * 分大小写:工具调用审查的规则(开头写了 `(?i)` 的除外)、写明了要分的出站脱敏 + * 自定义规则。内容过滤的不分 */ case_sensitive?: boolean, } | { "kind": "contains", text: string, /**