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
4 changes: 2 additions & 2 deletions scripts/shots/core/en/status.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"api_version": 34,
"api_version": 35,
"clients": 4,
"config_path": "",
"gateway_addr": null,
Expand All @@ -14,5 +14,5 @@
"reachable": []
},
"uptime_secs": 0,
"version": "0.58.0"
"version": "0.59.0"
}
4 changes: 2 additions & 2 deletions scripts/shots/core/zh/status.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"api_version": 34,
"api_version": 35,
"clients": 4,
"config_path": "",
"gateway_addr": null,
Expand All @@ -14,5 +14,5 @@
"reachable": []
},
"uptime_secs": 0,
"version": "0.58.0"
"version": "0.59.0"
}
5 changes: 4 additions & 1 deletion scripts/shots/mock/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ export const CORE: { [N in WebviewEndpoint]: Handler<N> } = {
// 产品图里没有插件:插件页是空的,写入一律拒绝
Plugins: () => [],
PluginInspect: refuse,
UpdatePlugin: refuse,
PluginRewrite: refuse,
CreatePlugin: refuse,
SavePlugin: refuse,
ApprovePluginFile: refuse,
PluginSourceDiff: refuse,
DeletePlugin: refuse,
ReorderPlugins: refuse,
Expand Down
28 changes: 14 additions & 14 deletions src-tauri/Cargo.lock

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

12 changes: 6 additions & 6 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.58.0" }
tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.58.0" }
tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.58.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.58.0" }
tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.58.0" }
tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.58.0" }
tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" }
tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" }
tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" }
tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" }
tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" }

[lib]
name = "thinkwatch_lite_lib"
Expand Down
65 changes: 42 additions & 23 deletions src-tauri/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
//! 命令拼好再给)。路径参数由 `tw_api::fill` 做百分号编码,所以界面给的名字
//! 只能是一段,拼不出别的路径。
//!
//! **插件的四步有意不给**:安装(`CreatePlugin`)、更换代码(`ReplacePluginSource`)、确认变了
//! 的文件(`ApprovePluginFile`),以及确认过的改动(`UpdatePluginConfirmed`:打开改得了回答
//! 里工具调用的插件、改它的设置或范围)。界面里的脚本自己就能点网页上的「确定」,所以这
//! 几步只能经过 `plugins` 里的命令,在系统原生对话框里确认(I12)。
//! **插件要点头的三步有意不给**:`CreatePluginConfirmed`、`SavePluginConfirmed`、
//! `ApprovePluginFileConfirmed`(约定附录 4 §3:装上、打开改得了回答里工具调用的插件,改它的
//! 代码,批准它磁盘上改过的文件)。界面里的脚本自己就能点网页上的「确定」,所以这几步只能
//! 经过 `plugins` 里的命令,在系统原生对话框里确认。不必点头的装、存、批准(`CreatePlugin`、
//! `SavePlugin`、`ApprovePluginFile`)在这里:要点头时 core 答 403。
//!
//! 做的事不止转发的命令(打开浏览器、写剪贴板、拼概览)仍然各是一个命令。
//! 其中有三个端点**只能经过那些命令**,因为这台机器上的客户端要一起照顾到:删密钥
Expand Down Expand Up @@ -137,11 +138,13 @@ webview_endpoints![
ChatgptResets,
UseChatgptReset,
ZaiLoginStatus,
// 插件。装、换代码、批准、确认过的改动走 Rust 这边的命令,见下面的测试。改得了工具调用
// 的插件,`UpdatePlugin` 在 core 那边只许停用、改出错时怎么办
// 插件。要点头的三步(`…Confirmed`)走 Rust 这边的命令,见下面的测试
Plugins,
PluginInspect,
UpdatePlugin,
PluginRewrite,
CreatePlugin,
SavePlugin,
ApprovePluginFile,
PluginSourceDiff,
DeletePlugin,
ReorderPlugins,
Expand Down Expand Up @@ -184,30 +187,46 @@ mod tests {
"DeleteKey",
"RotateKey",
"ClientKey",
// 插件的这几步要在原生对话框里确认(I12),见模块说明
"CreatePlugin",
"ReplacePluginSource",
"ApprovePluginFile",
"UpdatePluginConfirmed",
// 插件要点头的三步只能在原生对话框里确认,见模块说明
"CreatePluginConfirmed",
"SavePluginConfirmed",
"ApprovePluginFileConfirmed",
] {
assert!(!ALLOWED.contains(&name), "{name}");
}
}

/// 确认过的插件改动只有一条路:`plugin_update_confirmed` 先弹系统的确认框。界面的清单
/// 里连这个名字都不该有 —— 有了,网页里的脚本就能替用户打开一个改工具调用的插件
/// 要点头的插件写入只有一条路:`plugins` 里的命令先弹系统的确认框。界面的清单里连这几个
/// 名字都不该有 —— 有了,网页里的脚本就能替用户装上、打开、改写一个改工具调用的插件
#[test]
fn a_confirmed_plugin_update_only_goes_through_the_native_dialog() {
assert!(!ALLOWED.contains(&"UpdatePluginConfirmed"));
// 它确实是 core 的一个端点(不是拼错了名字才「不在清单里」)
assert!(
ep::ALL
.iter()
.any(|e| e.name == "UpdatePluginConfirmed" && e.path == "/plugins/{id}/confirmed")
);
fn the_confirmed_plugin_writes_only_go_through_the_native_dialog() {
let ts = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/../src/control.ts"))
.unwrap();
assert!(!ts.contains("\"UpdatePluginConfirmed\""));
for (name, path) in [
("CreatePluginConfirmed", "/plugins/confirmed"),
("SavePluginConfirmed", "/plugins/{id}/confirmed"),
(
"ApprovePluginFileConfirmed",
"/plugins/{id}/approve/confirmed",
),
] {
assert!(!ALLOWED.contains(&name), "{name}");
assert!(!ts.contains(&format!("\"{name}\"")), "{name}");
// 它确实是 core 的一个端点(不是拼错了名字才「不在清单里」)
assert!(
ep::ALL.iter().any(|e| e.name == name && e.path == path),
"{name}"
);
}
// 不必点头的那几步在清单里:要点头时 core 答 403,界面再请 Rust
for name in [
"CreatePlugin",
"SavePlugin",
"ApprovePluginFile",
"PluginRewrite",
] {
assert!(ALLOWED.contains(&name), "{name}");
}
}

/// 前端那份清单和这里一样。多一个,界面调了会被拒;少一个,界面上的类型
Expand Down
41 changes: 39 additions & 2 deletions src-tauri/src/core_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,20 @@ pub fn clause(m: &Msg) -> String {
}

/// 按码说中文,连同外面套的场合。说不出来是 None
///
/// **插件名按中文说**:`{plugin}` 是默认插件 core 发的英文名时,换成它的中文名(和插件页上
/// 一样,见 `plugins::defaults::name_in`)。消息里只有名字,按名字认
pub fn zh(m: &Msg) -> Option<String> {
let t = table();
if m.code.starts_with("//") {
return None;
}
let say = t.messages.get(&m.code)?;
// 填进中文句子的参数。认场合比的是英文原句,那边照旧用原样的参数
let mut zh_args = m.args.clone();
if let Some(p) = zh_args.get_mut("plugin") {
*p = crate::plugins::defaults::name_in(crate::i18n::Lang::Zh, None, p);
}
let mut leads = String::new();
let mut rest = m.text.as_str();
'peel: loop {
Expand All @@ -76,14 +84,14 @@ pub fn zh(m: &Msg) -> Option<String> {
else {
continue;
};
leads.push_str(&render(&c.zh, &m.args, &t.tables)?);
leads.push_str(&render(&c.zh, &zh_args, &t.tables)?);
leads.push(':');
rest = after;
continue 'peel;
}
break;
}
Some(leads + &render(say, &m.args, &t.tables)?)
Some(leads + &render(say, &zh_args, &t.tables)?)
}

// ------------------------------------------------------------ 写法
Expand Down Expand Up @@ -317,6 +325,35 @@ mod tests {
zh: String,
}

/// 默认插件的英文名在中文里换成插件页上的名字;英文照 core 的原句。别人的插件照它写的
#[test]
fn a_default_plugin_is_named_like_on_the_plugins_page() {
let m = |plugin: &str| Msg {
code: "control.plugin.needs_confirmation".into(),
args: BTreeMap::from([("plugin".into(), plugin.into())]),
text: format!("Plugin `{plugin}` can change the tool calls in replies, so …"),
};
let wsl = m("Convert WSL and Windows paths");
crate::i18n::with_lang(crate::i18n::Lang::Zh, || {
assert!(
text(&wsl).starts_with("插件「WSL 路径转换」可以修改"),
"{}",
text(&wsl)
);
let theirs = m("Rename tools");
assert!(
text(&theirs).starts_with("插件「Rename tools」"),
"{}",
text(&theirs)
);
});
crate::i18n::with_lang(crate::i18n::Lang::En, || {
assert_eq!(text(&wsl), wsl.text);
// 中文那一句不看当前语言
assert!(zh(&wsl).unwrap().contains("「WSL 路径转换」"));
});
}

/// 和界面跑同一份用例:两份实现对同一条消息说同一句话
#[test]
fn the_shared_cases_say_the_same_as_the_interface() {
Expand Down
7 changes: 3 additions & 4 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,9 @@ pub fn run() {
mcp::mcp_targets,
mcp::plan_mcp,
mcp::apply_mcp,
plugins::plugin_install,
plugins::plugin_replace_source,
plugins::plugin_approve,
plugins::plugin_update_confirmed,
plugins::plugin_install_confirmed,
plugins::plugin_save_confirmed,
plugins::plugin_approve_confirmed,
scan::scan_clients,
diagnostics::save_diagnostics,
])
Expand Down
7 changes: 4 additions & 3 deletions src-tauri/src/plugins/confirm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! 系统原生的确认对话框(I12)。
//!
//! **为什么不能在网页里问。**安装插件、更换代码、确认文件变更,是把一段会改写每一个请求的
//! 代码放进网关。网页里的「确定」,网页里的脚本自己就能点 —— 一段混进页面的脚本可以一声
//! 不响地装上一个插件。系统的对话框画在网页之外,脚本点不到、键盘事件也伪造不到。
//! **为什么不能在网页里问。**装上、打开一个改得了回答里工具调用的插件,改它的代码,确认它
//! 改过的文件,是让一段代码改写客户端将要执行的命令。网页里的「确定」,网页里的脚本自己就能
//! 点 —— 一段混进页面的脚本可以一声不响地装上这样一个插件。系统的对话框画在网页之外,脚本
//! 点不到、键盘事件也伪造不到。
//!
//! 三个平台各用自己的:macOS 是 `NSAlert`,Windows 是 `MessageBoxW`,Linux 是 GTK 的
//! `MessageDialog`(都是应用本来就链接着的东西,不多一个依赖)。
Expand Down
Loading
Loading