Harden auth/vault path sanitization and clean WeKnora docs
This commit is contained in:
@@ -171,13 +171,39 @@ fn agent_profile_home() -> PathBuf {
|
||||
.unwrap_or_else(|| PathBuf::from(".mnote-agent"))
|
||||
}
|
||||
|
||||
fn agent_profile_config_path(profile: &str) -> PathBuf {
|
||||
let home = agent_profile_home();
|
||||
/// profile 段只允许单层安全名,拒绝 `/`、`\`、`..`,防止拼到 profiles/ 外。
|
||||
fn sanitize_agent_profile_segment(profile: &str) -> Option<&str> {
|
||||
let profile = profile.trim();
|
||||
if profile.is_empty() || profile == "default" {
|
||||
return home.join("config.yaml");
|
||||
return None;
|
||||
}
|
||||
let candidate = home.join("profiles").join(profile);
|
||||
if profile.contains('/')
|
||||
|| profile.contains('\\')
|
||||
|| profile.contains('\0')
|
||||
|| profile == "."
|
||||
|| profile == ".."
|
||||
|| profile
|
||||
.split(['/', '\\'])
|
||||
.any(|seg| seg.is_empty() || seg == "." || seg == "..")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// 仅允许常见 profile 标识字符,避免奇怪路径段。
|
||||
if !profile
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(profile)
|
||||
}
|
||||
|
||||
fn agent_profile_config_path(profile: &str) -> PathBuf {
|
||||
let home = agent_profile_home();
|
||||
let Some(safe) = sanitize_agent_profile_segment(profile) else {
|
||||
return home.join("config.yaml");
|
||||
};
|
||||
let candidate = home.join("profiles").join(safe);
|
||||
if candidate.exists() {
|
||||
candidate.join("config.yaml")
|
||||
} else {
|
||||
@@ -388,6 +414,13 @@ pub async fn mnote_call(
|
||||
Extension(context): Extension<RequestContext>,
|
||||
Json(input): Json<ToolCallInput>,
|
||||
) -> Result<(StatusCode, HeaderMap, Json<Value>), WebError> {
|
||||
// 7-76:外部 AI 经 tools 调用时按读写工具要求 notes scope。
|
||||
let required = if is_read_tool(&input.tool_name) {
|
||||
crate::routes::api_access_token::SCOPE_NOTES_READ
|
||||
} else {
|
||||
crate::routes::api_access_token::SCOPE_NOTES_WRITE
|
||||
};
|
||||
crate::routes::api_access_token::ensure_scope(&context, required)?;
|
||||
let response_body = execute_mnote_tool_call(&state, &context, input).await?;
|
||||
Ok((StatusCode::OK, stamp_tool_headers(), Json(response_body)))
|
||||
}
|
||||
@@ -588,15 +621,6 @@ pub(crate) async fn execute_mnote_tool_call(
|
||||
.with_context(&context))
|
||||
}
|
||||
"mnote.knowledge_rag.status" => knowledge_rag::status(&state, &context, &input).await,
|
||||
"mnote.weknora.search"
|
||||
| "mnote.weknora.list_sources"
|
||||
| "mnote.weknora.get_source_status"
|
||||
| "mnote.weknora.open_reference" => Err(WebError::new(
|
||||
StatusCode::GONE,
|
||||
"mnote_weknora_tools_retired",
|
||||
"WeKnora 专用工具已从默认 agent manifest 移除;请改用 provider-neutral mnote.knowledge_rag.*,或显式启动 legacy WeKnora provider 调试。",
|
||||
)
|
||||
.with_context(&context)),
|
||||
"mnote.knowledge_rag.query" => knowledge_rag::query(&state, &context, &input).await,
|
||||
"mnote.knowledge_rag.section_context" => {
|
||||
knowledge_rag::section_context(&state, &context, &input).await
|
||||
@@ -905,8 +929,11 @@ fn required_capability_scope(tool_name: &str) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn declared_capability_scope_covers(declared: Option<&Vec<String>>, required: &[String]) -> bool {
|
||||
// required 为空时由 ensure_tool_capability_scope 短路。
|
||||
let Some(declared) = declared else {
|
||||
// 兼容旧调用方:缺省 capabilityScope 不改变既有执行路径。
|
||||
// 兼容旧调用方:完全未声明 capabilityScope 时不改变既有执行路径。
|
||||
// 写工具仍由 shared_read / aiAccessScope / commandContext 等合同 fail-closed。
|
||||
// 注意:显式声明 `[]` 与“未声明”语义不同——空数组表示调用方主动声明无能力,必须拒绝。
|
||||
return true;
|
||||
};
|
||||
let declared = declared
|
||||
@@ -914,6 +941,10 @@ fn declared_capability_scope_covers(declared: Option<&Vec<String>>, required: &[
|
||||
.map(|value| normalize_capability_scope(value))
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
// 显式空 capabilityScope → fail-closed(禁止 None 与 [] 混同为“放行”)。
|
||||
if declared.is_empty() {
|
||||
return false;
|
||||
}
|
||||
required.iter().all(|scope| {
|
||||
declared
|
||||
.iter()
|
||||
@@ -1311,6 +1342,7 @@ fn stamp_tool_headers() -> HeaderMap {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{agent_profile_config_path, sanitize_agent_profile_segment};
|
||||
use crate::app::{build_app, AppConfig, AppState};
|
||||
use axum::body::{to_bytes, Body};
|
||||
use axum::http::{Request, StatusCode};
|
||||
@@ -1325,6 +1357,37 @@ mod tests {
|
||||
crate::test_support::agent_env_lock()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_profile_segment_rejects_path_traversal() {
|
||||
assert!(sanitize_agent_profile_segment("ok-profile").is_some());
|
||||
assert!(sanitize_agent_profile_segment("../etc").is_none());
|
||||
assert!(sanitize_agent_profile_segment("a/b").is_none());
|
||||
assert!(sanitize_agent_profile_segment("..").is_none());
|
||||
assert!(sanitize_agent_profile_segment("default").is_none());
|
||||
// 危险段回落到 default config.yaml,路径中不得含攻击串
|
||||
let path = agent_profile_config_path("../../../etc/passwd");
|
||||
let s = path.to_string_lossy();
|
||||
assert!(!s.contains("etc/passwd"), "{s}");
|
||||
assert!(s.ends_with("config.yaml"), "{s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declared_capability_scope_empty_vec_is_fail_closed() {
|
||||
use super::declared_capability_scope_covers;
|
||||
let required = vec!["page.write".to_string()];
|
||||
// 未声明:兼容旧路径
|
||||
assert!(declared_capability_scope_covers(None, &required));
|
||||
// 显式空:拒绝
|
||||
let empty: Vec<String> = vec![];
|
||||
assert!(!declared_capability_scope_covers(Some(&empty), &required));
|
||||
// 显式覆盖:通过
|
||||
let ok = vec!["page.write".to_string()];
|
||||
assert!(declared_capability_scope_covers(Some(&ok), &required));
|
||||
// 仅 read 不覆盖 write
|
||||
let read_only = vec!["page.read".to_string()];
|
||||
assert!(!declared_capability_scope_covers(Some(&read_only), &required));
|
||||
}
|
||||
|
||||
fn app() -> axum::Router {
|
||||
build_app(AppState::new(AppConfig {
|
||||
service_name: "mnote-web".into(),
|
||||
@@ -1389,6 +1452,7 @@ mod tests {
|
||||
dev_user_id: "dev-user".into(),
|
||||
dev_user_name: "开发用户".into(),
|
||||
dev_user_email: "dev@mnote.local".into(),
|
||||
environment: "dev".into(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1439,6 +1503,7 @@ mod tests {
|
||||
dev_user_id: "dev-user".into(),
|
||||
dev_user_name: "开发用户".into(),
|
||||
dev_user_email: "dev@mnote.local".into(),
|
||||
environment: "dev".into(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2333,6 +2398,10 @@ mod tests {
|
||||
"dryRun": true,
|
||||
"capabilityScope": ["office.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"address": "A1",
|
||||
"value": "after"
|
||||
}
|
||||
@@ -2476,6 +2545,10 @@ mod tests {
|
||||
"dryRun": true,
|
||||
"capabilityScope": ["office.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"onlyofficeSessionId": bridge_session_id,
|
||||
"address": "A1",
|
||||
"value": "after"
|
||||
@@ -4951,6 +5024,10 @@ mod tests {
|
||||
"dryRun": true,
|
||||
"capabilityScope": ["page.write", "block.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"command": "block_replace",
|
||||
"blockId": "heading_1",
|
||||
"content": "替换标题"
|
||||
@@ -4990,6 +5067,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"anchorBlockId": "heading_1",
|
||||
"revision": 7,
|
||||
@@ -5041,6 +5122,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"content": "替换后的章节",
|
||||
"revision": 7,
|
||||
@@ -5089,6 +5174,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"anchorBlockId": "heading_1",
|
||||
"content": "新增段落",
|
||||
"revision": 7,
|
||||
@@ -5137,6 +5226,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "p_1",
|
||||
"revision": 7,
|
||||
"conflictDetectionKey": "doc_1:7",
|
||||
@@ -5184,6 +5277,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"anchorBlockId": "p_2",
|
||||
"revision": 7,
|
||||
@@ -5228,6 +5325,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"content": "越界替换",
|
||||
"revision": 7,
|
||||
@@ -5248,6 +5349,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"anchorBlockId": "heading_1",
|
||||
"content": "越界插入",
|
||||
"revision": 7,
|
||||
@@ -5268,6 +5373,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "p_1",
|
||||
"revision": 7,
|
||||
"conflictDetectionKey": "doc_1:7",
|
||||
@@ -5287,6 +5396,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"anchorBlockId": "p_2",
|
||||
"revision": 7,
|
||||
@@ -5394,6 +5507,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": block_id,
|
||||
"anchorBlockId": "p_anchor",
|
||||
"revision": 7,
|
||||
@@ -5445,6 +5562,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"anchorBlockId": "heading_1",
|
||||
"blocks": ["新增第一段", {"type": "todo", "content": "新增待办"}],
|
||||
"revision": 7,
|
||||
@@ -5507,6 +5628,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"anchorBlockId": "heading_1",
|
||||
"blocks": blocks,
|
||||
"revision": 7,
|
||||
@@ -5552,6 +5677,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"content": "不应写入"
|
||||
}
|
||||
@@ -5591,6 +5720,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"blockId": "heading_1",
|
||||
"content": "不应写入",
|
||||
"revision": 7,
|
||||
@@ -5636,6 +5769,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"operations": [{
|
||||
"op": "replace",
|
||||
"blockId": "p_2",
|
||||
@@ -5678,6 +5815,10 @@ mod tests {
|
||||
"dryRun": false,
|
||||
"capabilityScope": ["block.write", "page.write"],
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"revision": 7,
|
||||
"conflictDetectionKey": "doc_1:7",
|
||||
"operations": [{
|
||||
@@ -6016,7 +6157,11 @@ mod tests {
|
||||
"args": {
|
||||
"content": [
|
||||
{"type": "paragraph", "content": [{"type": "text", "text": "本地 page.save 写入"}]}
|
||||
]
|
||||
],
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write",
|
||||
"allowedResourceIds": ["local-md:README.md"]
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
@@ -6097,6 +6242,10 @@ mod tests {
|
||||
"wideLayout": true,
|
||||
"showHeadingNumbers": true,
|
||||
"hideTitleHeader": false
|
||||
},
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write",
|
||||
"allowedResourceIds": ["local-md:README.md"]
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -6158,7 +6307,11 @@ mod tests {
|
||||
"traceId": "trace_1",
|
||||
"idempotencyKey": "idem_save_1",
|
||||
"dryRun": true,
|
||||
"args": {"content": [{"type":"paragraph","content":[{"type":"text","text":"AI 写入"}]}]}
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
"content": [{"type":"paragraph","content":[{"type":"text","text":"AI 写入"}]}]}
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
@@ -6238,8 +6391,12 @@ mod tests {
|
||||
));
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
fs::create_dir_all(&root).expect("create temp root");
|
||||
let path = root.join("page.md");
|
||||
fs::write(&path, "第一段\n\n第二段\n").expect("write markdown");
|
||||
fs::write(&root.join("page.md"), "第一段\n\n第二段\n").expect("write markdown");
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
crate::routes::local_folder_source::initialize_local_workspace_for_actor(
|
||||
"user_1", &root_uri,
|
||||
)
|
||||
.expect("initialize workspace");
|
||||
|
||||
let response = app()
|
||||
.oneshot(
|
||||
@@ -6248,11 +6405,15 @@ mod tests {
|
||||
.uri("/api/mnote/tools/call")
|
||||
.header("content-type", "application/json")
|
||||
.header("x-mnote-actor-id", "user_1")
|
||||
.header("x-mnote-actor-type", "user")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"toolName": "mnote.doc.markdown_edit",
|
||||
"workspaceId": "ws_demo",
|
||||
"documentId": path.to_string_lossy(),
|
||||
"workspaceId": "local-ws-dry-run",
|
||||
"documentId": "local-md:page.md",
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": root_uri,
|
||||
"actorId": "user_1",
|
||||
"sessionId": "sess_1",
|
||||
"runId": "run_1",
|
||||
"toolCallId": "call_1",
|
||||
@@ -6260,6 +6421,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_local_1",
|
||||
"dryRun": true,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write",
|
||||
"allowedResourceIds": ["local-md:page.md"]
|
||||
},
|
||||
"operations": [{"search": "第二段", "replace": "测试123"}]
|
||||
}
|
||||
})
|
||||
@@ -6277,10 +6442,9 @@ mod tests {
|
||||
let payload: Value = serde_json::from_slice(&body).expect("json");
|
||||
assert_eq!(payload["audit"]["effect"], "dry_run");
|
||||
assert_eq!(payload["result"]["operationsApplied"], 1);
|
||||
assert_eq!(payload["result"]["applyResult"]["written"], false);
|
||||
assert_eq!(payload["result"]["applyResult"]["dryRun"], true);
|
||||
// dry-run 不得改盘
|
||||
assert_eq!(
|
||||
fs::read_to_string(&path).expect("read markdown"),
|
||||
fs::read_to_string(root.join("page.md")).expect("read markdown"),
|
||||
"第一段\n\n第二段\n"
|
||||
);
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
@@ -6475,6 +6639,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_mapping_empty_2",
|
||||
"dryRun": false,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"operations": [{"search": "第二段 <!-- block:p_2 -->", "replace": "测试123"}]
|
||||
}
|
||||
})
|
||||
@@ -6519,6 +6687,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_full_content_online_2",
|
||||
"dryRun": false,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"full_content": "章节一\n\n第一段已改\n\n第二段已改"
|
||||
}
|
||||
})
|
||||
@@ -6562,6 +6734,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_normalized_1",
|
||||
"dryRun": true,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"operations": [{"search": "第二 段", "replace": "测试123"}]
|
||||
}
|
||||
})
|
||||
@@ -6612,6 +6788,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_precondition",
|
||||
"dryRun": false,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"operations": [{"search": "第二段", "replace": "测试123"}]
|
||||
}
|
||||
})
|
||||
@@ -6676,7 +6856,11 @@ mod tests {
|
||||
"idempotencyKey": "idem_local_folder_md",
|
||||
"dryRun": false,
|
||||
"args": {
|
||||
"operations": [{"search": "第一段", "replace": "第一段已由 AI 修改"}]
|
||||
"operations": [{"search": "第一段", "replace": "第一段已由 AI 修改"}],
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write",
|
||||
"allowedResourceIds": ["local-md:README.md"]
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
@@ -6730,6 +6914,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_noop",
|
||||
"dryRun": false,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"operations": [{"search": "不存在的段落", "replace": "测试123"}]
|
||||
}
|
||||
})
|
||||
@@ -6771,6 +6959,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_scope",
|
||||
"dryRun": true,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"allowedTargetBlockIds": ["p_2"],
|
||||
"operations": [{"search": "第一段", "replace": "不应越权修改"}]
|
||||
}
|
||||
@@ -6813,6 +7005,10 @@ mod tests {
|
||||
"idempotencyKey": "idem_markdown_same_block_1",
|
||||
"dryRun": true,
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
|
||||
"operations": [
|
||||
{"search": "二", "replace": "2"},
|
||||
{"search": "2段", "replace": "2段落"}
|
||||
@@ -6861,7 +7057,11 @@ mod tests {
|
||||
"traceId": "trace_1",
|
||||
"idempotencyKey": "idem_options_1",
|
||||
"dryRun": true,
|
||||
"args": {"options": {"wideLayout": true, "pageFont": "serif"}}
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
"options": {"wideLayout": true, "pageFont": "serif"}}
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
@@ -6904,7 +7104,11 @@ mod tests {
|
||||
"traceId": "trace_1",
|
||||
"idempotencyKey": "idem_summary_1",
|
||||
"dryRun": true,
|
||||
"args": {"summary": "摘要内容"}
|
||||
"args": {
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write"
|
||||
},
|
||||
"summary": "摘要内容"}
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
@@ -6958,7 +7162,13 @@ mod tests {
|
||||
"traceId": "trace_artifact_local",
|
||||
"idempotencyKey": "idem_artifact_local",
|
||||
"dryRun": false,
|
||||
"args": {"summary": "本地摘要"}
|
||||
"args": {
|
||||
"summary": "本地摘要",
|
||||
"aiAccessScope": {
|
||||
"permissionLevel": "read_write",
|
||||
"allowedResourceIds": ["local-md:README.md"]
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user