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: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# 桌面端按行读消息码清单,换行不能随平台变
crates/tw-api/msg-codes.txt text eol=lf
# 默认插件随 core 一起发:源码按字节算哈希(装上、换新版都拿它比),预先算好的
# manifest 也记着这份哈希。各平台检出、编进二进制的字节必须一样
crates/tw-gateway/src/plugin/defaults/*.js text eol=lf
crates/tw-gateway/src/plugin/defaults/manifests.json text eol=lf
15 changes: 12 additions & 3 deletions bin/twcore/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,15 @@ fn cmd_serve(path: &Path, port: Option<u16>, safe: bool, parent: Option<u32>) ->
state.clone(),
state.bus.clone(),
));
// 默认插件(随 core 发的那几个):没给过的装上(停用着),没动过的换成新版。
// 启动时在控制面起来之前走一遍,界面第一次取插件就看得到它们;之后每换入一份
// 配置再走一遍。**不挡启动**:哪个没办成只记一行、说一声。安全模式不走 ——
// 那时只有控制面,不替人往配置里写东西
if !safe {
let seeder = tw_control::plugins::defaults::Seeder::shipped();
seeder.seed(&manager).await;
tw_control::plugins::defaults::spawn(seeder, manager.clone());
}
// **监听要留着** —— 扔掉它就停止监听,而那个失效是静默的。
// 起不来不是致命的:手改文件不会自动生效,但界面和 CLI 照常能用,
// 所以说一句就继续。
Expand Down Expand Up @@ -876,9 +885,9 @@ fn cmd_serve(path: &Path, port: Option<u16>, safe: bool, parent: Option<u32>) ->
chatgpt: Default::default(),
zai: Default::default(),
};
// 凭据轮换要写回 config.yaml。**这是这个程序里唯一一次
// 不是人发起的配置写入** —— 理由是服务器换发新 refresh token 的
// 那一刻旧的就作废了,不写回等于让配置文件从那一秒起就是坏的。
// 凭据轮换要写回 config.yaml。**不是人发起的配置写入只有两种**,
// 这是一种(另一种是上面的默认插件)—— 理由是服务器换发新 refresh
// token 的那一刻旧的就作废了,不写回等于让配置文件从那一秒起就是坏的。
tw_control::rotation::spawn(control.clone());
// 定期刷新默认价目表(`pricing.auto_update`,默认开)
tw_control::pricing::spawn(control.clone());
Expand Down
1 change: 1 addition & 0 deletions crates/tw-api/msg-codes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ control.plugin.blank_pattern
control.plugin.file_missing
control.plugin.file_moved_on
control.plugin.id_taken
control.plugin.needs_confirmation
control.plugin.not_found
control.plugin.order
control.plugin.reserved_id
Expand Down
18 changes: 13 additions & 5 deletions crates/tw-api/src/ep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ endpoints! {

// ─────────────────────────────────────────────── 脚本插件
//
// **装、换源码、批准三个端点不给网页调**(桌面端的 `call` 白名单里没有它们):
// 这三件事要在系统的确认框里点头,那一步在桌面端的 Rust 里 —— 它自己再编一遍
// 源码,把名字、权限和哈希摆给人看,点了头才发请求。网页里的脚本做不到这件事,
// 就做不成这三件事。
// **装、换源码、批准、确认过的改动四个端点不给网页调**(桌面端的 `call` 白名单里
// 没有它们):这几件事要在系统的确认框里点头,那一步在桌面端的 Rust 里 —— 它自己
// 再编一遍源码(或者读一遍插件现在的样子),把名字、权限和要改的地方摆给人看,点了
// 头才发请求。网页里的脚本做不到这件事,就做不成这几件事。
/// 全部插件,按运行的顺序:状态、计数
Plugins: GET "/plugins", () => Vec<api::PluginView>;
/// 编一份源码看看它是什么插件。**什么都不留下**
Expand All @@ -143,8 +143,16 @@ endpoints! {
CreatePlugin: POST "/plugins", api::PluginCreate => api::ConfigWritten;
/// 排顺序,也就是运行的顺序
ReorderPlugins: PUT "/plugins/order", api::PluginOrder => api::ConfigWritten;
/// 开关、出错时怎么办、范围、设置
/// 开关、出错时怎么办、范围、设置。**改得了回答里工具调用的插件**(权限有
/// `reply_tool_calls`,或者读不出它要什么权限),打开它、改它的设置或范围在这里一律
/// 拒绝(403,`control.plugin.needs_confirmation`),要走 `UpdatePluginConfirmed`;
/// 停用、改出错时怎么办照常
UpdatePlugin: PUT "/plugins/{id}" [id], api::PluginUpdate => api::ConfigWritten;
/// 同一件事,在系统的确认框里点过头了:工具调用插件的开关、设置、范围也改得了。
/// **网页不能调,桌面端也不许把它放进网页的白名单**:网页里注入的脚本调得到它,就能
/// 自己打开一个改工具调用的插件、改它的设置。桌面端的 Rust 先弹系统的确认框(插件
/// 的名字、它能做什么、这次改了什么),点了头再发
UpdatePluginConfirmed: PUT "/plugins/{id}/confirmed" [id], api::PluginUpdate => api::ConfigWritten;
/// 删掉:配置里那一条、插件文件和底稿
DeletePlugin: DELETE "/plugins/{id}" [id], api::BaseVersion => api::ConfigWritten;
/// 换一份源码,批准的就是新的这一份。**网页不能调**
Expand Down
20 changes: 17 additions & 3 deletions crates/tw-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ slug_enum! {
Rollback = "rollback",
/// OAuth 凭据轮换之后写回
Rotation = "rotation",
/// core 自己:装上它自带的默认插件,或者把没动过的默认插件换成新版
Defaults = "defaults",
}
}

Expand Down Expand Up @@ -682,6 +684,13 @@ pub const MSG_CODES: &str = include_str!("../msg-codes.txt");
/// 尝试链的第几跳)和 `request_after_plugins`(插件改过的请求体),[`HistoryRow`] 多了
/// `plugin_changed`。装、换源码、批准三个端点不给网页调:要在系统的确认框里点头。照 32
/// 写的界面看不到插件。
///
/// 33 起**改得了工具调用的插件要点过头才能打开**:`UpdatePlugin` 拒绝打开权限里有
/// `reply_tool_calls` 的插件(读不出权限的也算)、改它的设置或范围(403,
/// `control.plugin.needs_confirmation`),这几样走新端点 `PUT /plugins/{id}/confirmed`
/// (`UpdatePluginConfirmed`,请求体同 [`PluginUpdate`])—— 它和装、换源码、批准一样
/// 不给网页调,桌面端在系统的确认框里点了头才发。同一版起 core 自带几个默认插件,第一次
/// 见到时装上、停用着,写配置的这一版来源是 [`ConfigOrigin::Defaults`]。
pub const CONTROL_API_VERSION: u32 = 33;

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -1263,7 +1272,7 @@ pub enum Event {
id: u64,
/// 内容版本号,和 `PATCH /config` 的 `base_version` 是同一个
version: String,
/// `ui` / `cli` / `external` / `rollback` / `rotation`
/// `ui` / `cli` / `external` / `rollback` / `rotation` / `defaults`
origin: ConfigOrigin,
at_ms: u64,
},
Expand All @@ -1281,7 +1290,7 @@ pub enum Event {
line: Option<usize>,
/// 出错那一行的原文,**已脱敏**
excerpt: Option<String>,
/// 这一版是谁写的:`ui` / `cli` / `external` / `rollback` / `rotation`。
/// 这一版是谁写的:`ui` / `cli` / `external` / `rollback` / `rotation` / `defaults`。
///
/// **界面靠它区分「用户在编辑器里写错了」和「界面自己刚写坏了」** ——
/// 前者要提醒,后者是保存失败,那条路自己会报。
Expand Down Expand Up @@ -3125,7 +3134,7 @@ pub struct BaseVersion {
pub struct ConfigVersion {
pub version: String,
pub at_ms: u64,
/// `ui` / `cli` / `external` / `rollback` / `rotation`
/// `ui` / `cli` / `external` / `rollback` / `rotation` / `defaults`
pub origin: ConfigOrigin,
pub bytes: u64,
/// 这一版是现在跑着的那一版吗。
Expand Down Expand Up @@ -5128,6 +5137,11 @@ pub struct PluginCreate {

/// 改一个插件的开关、出错时怎么办、范围、设置(`PUT /plugins/{id}`)。**整份交**:
/// 交上来的就是保存之后的样子。
///
/// 插件改得了回答里的工具调用(权限有 [`Permission::ReplyToolCalls`],或者读不出它要
/// 什么权限)时,打开它、改设置、改范围这条路不收(`control.plugin.needs_confirmation`),
/// 同一份请求体交给 `PUT /plugins/{id}/confirmed`:那个端点网页调不了,桌面端在系统的
/// 确认框里点了头才发。比的是生效的值:没写进配置的设置按默认值算,范围不看顺序。
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
pub struct PluginUpdate {
Expand Down
9 changes: 8 additions & 1 deletion crates/tw-api/src/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ mod tests {
);
}

/// 插件:设置值是那个值本身,状态按 `kind` 分派,三个要系统确认框的端点照样在表里
/// 插件:设置值是那个值本身,状态按 `kind` 分派,四个要系统确认框的端点照样在表里
/// (网页白名单在桌面端,不在这里)
#[test]
fn plugins_come_through() {
Expand Down Expand Up @@ -368,11 +368,18 @@ mod tests {
" CreatePlugin: { req: PluginCreate; res: ConfigWritten };",
" ReplacePluginSource: { req: PluginSourceReplace; res: ConfigWritten };",
" ApprovePluginFile: { req: PluginApprove; res: ConfigWritten };",
" UpdatePluginConfirmed: { req: PluginUpdate; res: ConfigWritten };",
" UpdatePluginConfirmed: { method: \"PUT\", path: \"/plugins/{id}/confirmed\", params: [\"id\"], format: \"json\" },",
" DeletePlugin: { req: BaseVersion; res: ConfigWritten };",
" TrialPlugin: { req: PluginTrial; res: PluginTrialResult };",
] {
assert!(ts.contains(line), "{line}");
}
// core 自己写配置(默认插件)的那一版有自己的来源
assert_eq!(
decl_of(&ts, "ConfigOrigin"),
"export type ConfigOrigin = \"ui\" | \"cli\" | \"external\" | \"rollback\" | \"rotation\" | \"defaults\""
);
}

#[test]
Expand Down
Loading
Loading