Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent), MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
commit
bc41dfcf7d
517 files changed
+92844
No files matched your search
@@ -0,0 +1,110 @@
|
||||
//! `list_accounts`:账户清单(**凭据永不出口**)。
|
||||
//!
|
||||
//! 这个工具存在的理由很具体:用户常问「我有哪几个号 / 这局用的是哪个号」。
|
||||
//! 但 D4/D9 的凭据红线是硬的——工具结果会回灌模型、也可能被发往远端 LLM,
|
||||
//! 所以:
|
||||
//! 1. 端口只回 [`crate::port::AccountSummary`](`AccountMeta` 的公开字段);
|
||||
//! 2. 本文件里**没有任何**能表达 token / 密码的类型(连字段名都不出现);
|
||||
//! 3. 工具结果再过一遍 [`crate::redact`],兜住实现方把凭据塞进 `profile_name` 的意外。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::port::AccountPort;
|
||||
use crate::registry::schema::object;
|
||||
use crate::registry::{
|
||||
RegistryError, ToolCtx, ToolError, ToolFn, ToolOutput, ToolRegistry, ToolSpec,
|
||||
};
|
||||
|
||||
/// 工具名。
|
||||
pub const LIST_ACCOUNTS: &str = "list_accounts";
|
||||
|
||||
/// 注册 `list_accounts`。
|
||||
///
|
||||
/// # Errors
|
||||
/// 工具名重复/schema 非法时返回 [`RegistryError`]。
|
||||
pub fn register(
|
||||
registry: &mut ToolRegistry,
|
||||
port: Arc<dyn AccountPort>,
|
||||
) -> Result<(), RegistryError> {
|
||||
let handler: ToolFn = Arc::new(move |_ctx: &ToolCtx, _args: &Value| {
|
||||
let accounts = port.list_accounts().map_err(ToolError::Port)?;
|
||||
let count = accounts.len();
|
||||
let display: Vec<String> = accounts
|
||||
.iter()
|
||||
.map(|a| {
|
||||
if a.profile_name.trim().is_empty() {
|
||||
format!("{}({})", a.id, a.kind)
|
||||
} else {
|
||||
format!("{}({})", a.profile_name, a.kind)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut content = json!({
|
||||
"count": count,
|
||||
"accounts": accounts,
|
||||
"credential_policy": "账户凭据(access token / 刷新令牌 / authlib 密码)永不经由 AI 工具出口",
|
||||
});
|
||||
// 兜底脱敏:端口实现若把敏感串塞进了公开字段,这里也不会带出去。
|
||||
content = crate::redact::redact_texts(&content);
|
||||
Ok(ToolOutput::ok(
|
||||
content,
|
||||
if count == 0 {
|
||||
"还没有任何账户".to_string()
|
||||
} else {
|
||||
format!("账户 {count} 个:{}", display.join("、"))
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
registry.register(ToolSpec::read_only(
|
||||
LIST_ACCOUNTS,
|
||||
"列出启动器里已登录的账户(展示名 / 轨道 offline|microsoft|authlib / 档案 UUID / 创建时间)。**永不返回任何凭据**:令牌与密码不在工具结果的类型里,需要登录态请让用户在启动器界面查看。",
|
||||
object(&[], &[]),
|
||||
handler,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::testing::MockAccounts;
|
||||
|
||||
fn registry(port: Arc<dyn AccountPort>) -> ToolRegistry {
|
||||
let mut registry = ToolRegistry::new();
|
||||
register(&mut registry, port).expect("注册应成功");
|
||||
registry
|
||||
}
|
||||
|
||||
fn call(registry: &ToolRegistry) -> Value {
|
||||
let spec = registry.get(LIST_ACCOUNTS).expect("工具存在").clone();
|
||||
let ctx = ToolCtx {
|
||||
agent_session_id: "s1".to_string(),
|
||||
confirmed: false,
|
||||
};
|
||||
(spec.handler)(&ctx, &json!({})).expect("应成功").content
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lists_accounts_without_credentials() {
|
||||
let content = call(®istry(Arc::new(MockAccounts::new())));
|
||||
assert_eq!(content["count"], json!(1));
|
||||
assert_eq!(content["accounts"][0]["profile_name"], json!("Steve"));
|
||||
// 只扫账户数据本身:`credential_policy` 那句说明文字里本来就写着 "token"。
|
||||
let fields = content["accounts"].to_string().to_lowercase();
|
||||
for forbidden in ["token", "password", "refresh", "secret"] {
|
||||
assert!(
|
||||
!fields.contains(forbidden),
|
||||
"账户字段含 `{forbidden}`:{fields}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_list_is_not_an_error() {
|
||||
let content = call(®istry(Arc::new(MockAccounts::empty())));
|
||||
assert_eq!(content["count"], json!(0));
|
||||
assert_eq!(content["accounts"], json!([]));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user