mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-25 17:22:27 +08:00
feat(omp): manage Oh My Pi global prompts
Oh My Pi reads its user-level instructions from AGENTS.md in the profile-scoped agent directory, so prompts now project there like Kimi Code's. Drop the prompt-sync skip and the stale deep-link notes that assumed there was no prompt panel.
This commit is contained in:
1 parent
e88a9fa417
commit
b34753b832
7 files changed
+46
-26
No files matched your search
@@ -91,9 +91,9 @@ fn parse_provider_deeplink(
|
||||
// Validate app type.
|
||||
//
|
||||
// Oh My Pi is refused with the same guidance as the import path
|
||||
// (`deeplink/provider.rs`): its providers are created from the Oh My Pi
|
||||
// page, where the models.yml text editor can compose the entry. Accepting
|
||||
// the link here would only fail later with a generic "invalid app" error.
|
||||
// (`deeplink/provider.rs`): a models.yml entry needs its models listed, which
|
||||
// the Oh My Pi provider form composes. Accepting the link here would only
|
||||
// fail later with a generic "invalid app" error.
|
||||
if matches!(app.as_str(), "omp" | "ohmypi" | "oh-my-pi" | "oh_my_pi") {
|
||||
return Err(AppError::InvalidInput(
|
||||
"Oh My Pi providers must be added from the Oh My Pi provider page".to_string(),
|
||||
@@ -253,13 +253,7 @@ fn parse_prompt_deeplink(
|
||||
.ok_or_else(|| AppError::InvalidInput("Missing 'app' parameter for prompt".to_string()))?
|
||||
.clone();
|
||||
|
||||
// Validate app type.
|
||||
//
|
||||
// Oh My Pi is deliberately absent: it has no prompt panel, so accepting a
|
||||
// link for it would let an import write `<agent dir>/AGENTS.md` through
|
||||
// `PromptService`, with no surface in the app showing what landed there and
|
||||
// no way to undo it. The prompt file mapping stays in place for the day a
|
||||
// panel exists; until then the link is refused at parse time.
|
||||
// Validate app type
|
||||
if !matches!(
|
||||
app.as_str(),
|
||||
"claude"
|
||||
|
||||
@@ -233,7 +233,7 @@ fn test_parse_mcp_deeplink_accepts_pi_mcode_and_omp() {
|
||||
/// 供应商链接对 Oh My Pi 明确拒绝,而不是落到「Invalid provider app type」。
|
||||
#[test]
|
||||
fn test_parse_provider_deeplink_refuses_omp_with_guidance() {
|
||||
for app in ["omp", "omp", "oh-my-pi", "oh_my_pi"] {
|
||||
for app in ["omp", "ohmypi", "oh-my-pi", "oh_my_pi"] {
|
||||
let url =
|
||||
format!("ccswitch://v1/import?resource=provider&app={app}&name=Test&endpoint=https%3A%2F%2Fapi.example.com");
|
||||
let err = parse_deeplink_url(&url).unwrap_err().to_string();
|
||||
@@ -252,15 +252,6 @@ fn test_parse_prompt_deeplink_accepts_mcode() {
|
||||
assert_eq!(request.app.as_deref(), Some("mcode"));
|
||||
}
|
||||
|
||||
/// Oh My Pi has no prompt panel: the link must be refused instead of writing
|
||||
/// `~/.omp/agent/AGENTS.md` with nothing in the UI showing what landed there.
|
||||
#[test]
|
||||
fn test_parse_prompt_deeplink_refuses_omp() {
|
||||
let url = "ccswitch://v1/import?resource=prompt&app=omp&name=Rules&content=IyBoaQ%3D%3D";
|
||||
let err = parse_deeplink_url(url).unwrap_err().to_string();
|
||||
assert!(err.contains("Invalid app type"), "{err}");
|
||||
}
|
||||
|
||||
/// 读取不清除,按编号确认才清除:前端读到后、显示前被打断(effect 重建、窗口销毁),
|
||||
/// 下次读取仍能拿到;期间来了新链接,迟到的旧确认不能把新链接清掉
|
||||
#[test]
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) fn validate_prompt_content(app: &AppType, content: &str) -> Result<()
|
||||
|
||||
/// 返回指定应用所使用的提示词文件路径。
|
||||
pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
||||
if matches!(app, AppType::ClaudeDesktop | AppType::Omp) {
|
||||
if matches!(app, AppType::ClaudeDesktop) {
|
||||
return Err(AppError::localized(
|
||||
"app.prompts_unsupported",
|
||||
"当前应用暂不支持 Prompts",
|
||||
@@ -41,7 +41,9 @@ pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
||||
// agy 只从各「定制根目录」读独立的 AGENTS.md;全局那一个是 ~/.gemini/config,Key 模式的
|
||||
// 私有 --gemini_dir 也经软链共享它。别用 ~/.gemini/GEMINI.md:那是 Gemini CLI 的。
|
||||
AppType::Agy => crate::agy_config::customizations_dir(),
|
||||
AppType::ClaudeDesktop | AppType::Omp => unreachable!("handled above"),
|
||||
// omp 只读按 profile 切换的 agent 目录里的 AGENTS.md(上游 discovery/builtin.ts)
|
||||
AppType::Omp => crate::omp_config::get_omp_agent_dir()?,
|
||||
AppType::ClaudeDesktop => unreachable!("handled above"),
|
||||
};
|
||||
|
||||
let filename = match app {
|
||||
@@ -50,9 +52,8 @@ pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
||||
AppType::Gemini => "GEMINI.md",
|
||||
AppType::GrokBuild | AppType::OpenCode | AppType::OpenClaw => "AGENTS.md",
|
||||
AppType::Hermes => "SOUL.md",
|
||||
AppType::Pi | AppType::Mcode | AppType::Agy | AppType::Kimi => "AGENTS.md",
|
||||
|
||||
AppType::ClaudeDesktop | AppType::Omp => unreachable!("handled above"),
|
||||
AppType::Pi | AppType::Mcode | AppType::Agy | AppType::Kimi | AppType::Omp => "AGENTS.md",
|
||||
AppType::ClaudeDesktop => unreachable!("handled above"),
|
||||
};
|
||||
|
||||
Ok(base_dir.join(filename))
|
||||
|
||||
@@ -281,7 +281,7 @@ impl PromptService {
|
||||
pub fn sync_to_live(state: &AppState, app: AppType) -> Result<(), AppError> {
|
||||
// Pi derives activation from its native AGENTS.md; its persisted prompt
|
||||
// rows are intentionally disabled and must not drive generic projection.
|
||||
if matches!(app, AppType::ClaudeDesktop | AppType::Pi | AppType::Omp) {
|
||||
if matches!(app, AppType::ClaudeDesktop | AppType::Pi) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -193,3 +193,35 @@ fn saving_inactive_prompts_keeps_a_hand_written_file_until_the_last_enabled_one_
|
||||
update_settings(AppSettings::default()).unwrap();
|
||||
let _ = fs::remove_dir_all(&hermes_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omp_prompts_write_agents_md_in_the_agent_directory() {
|
||||
let _guard = test_mutex().lock().unwrap();
|
||||
reset_test_fs();
|
||||
let home = ensure_test_home();
|
||||
let agent_dir = home.join("custom-omp/agent");
|
||||
update_settings(AppSettings {
|
||||
omp_config_dir: Some(agent_dir.to_string_lossy().into_owned()),
|
||||
..AppSettings::default()
|
||||
})
|
||||
.unwrap();
|
||||
let state = create_test_state().unwrap();
|
||||
|
||||
let prompt = Prompt {
|
||||
id: "global".into(),
|
||||
name: "Global".into(),
|
||||
content: "Use the project conventions.".into(),
|
||||
description: None,
|
||||
enabled: false,
|
||||
created_at: None,
|
||||
updated_at: None,
|
||||
};
|
||||
PromptService::upsert_prompt(&state, AppType::Omp, "global", prompt).unwrap();
|
||||
PromptService::enable_prompt(&state, AppType::Omp, "global").unwrap();
|
||||
assert_eq!(
|
||||
fs::read_to_string(agent_dir.join("AGENTS.md")).unwrap(),
|
||||
"Use the project conventions."
|
||||
);
|
||||
|
||||
update_settings(AppSettings::default()).unwrap();
|
||||
}
|
||||
@@ -22,6 +22,7 @@ const PROMPT_FILE_NAME: Partial<Record<AppId, string>> = {
|
||||
pi: "AGENTS.md",
|
||||
mcode: "AGENTS.md",
|
||||
kimi: "AGENTS.md",
|
||||
omp: "AGENTS.md",
|
||||
};
|
||||
|
||||
export function promptFileName(app: AppId): string {
|
||||
|
||||
@@ -23,6 +23,7 @@ export const PROMPT_APP_IDS: AppId[] = [
|
||||
"mcode",
|
||||
"kimi",
|
||||
"agy",
|
||||
"omp",
|
||||
];
|
||||
|
||||
/** 应用页对应的提示词应用;不支持提示词的返回 null。 */
|
||||
|
||||
Reference in new issue
Block a user