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
2 changes: 1 addition & 1 deletion MiniApp/Skills/miniapp-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ MiniApp 框架**只暴露下列能力**,没有任何"通用 BitFun 后端通
| AI | `app.ai.complete / chat / cancel / getModels` | 复用宿主 AIClient,受 `permissions.ai`(含 `allowed_models` / 速率限制) |
| 对话框 | `app.dialog.open/save/message` | Tauri dialog 插件 |
| 剪贴板 | `app.clipboard.readText/writeText` | 宿主 navigator.clipboard |
| Agent 会话 | `app.agent.run / cancel / turnText / cancelStaleRuns / onEvent` | 受 `permissions.agent.enabled` 限制;启动小应用自己的隐藏 agent 回合,事件只回流到发起的小应用 |
| Agent 会话 | `app.agent.run / cancel / turnText / cancelStaleRuns / onEvent` | 受 `permissions.agent.enabled` 限制;启动小应用自己的隐藏 agent 回合,事件只回流到发起的小应用。工具集按运行时档位收敛:市场小应用(`runtime_profile = market_strict`)只保留 `WebSearch` / `WebFetch` 这类只读联网调研工具,碰不到文件系统、命令行和宿主控制面;内置 / `compatibility` 档位保留完整的 headless 工具集 |
| 悬浮会话气泡 | `app.chat.claimComposer / releaseComposer / focusSession / setComposerDraft / onUserMessage` | 受 `permissions.agent.enabled` 限制;把内容和提交路由注册进右下角的标准悬浮聊天窗(输入器、附件、模型、权限、停止等仍由宿主共享组件拥有),并展示小应用自己的 Agent 过程(Agentic MiniApp 模式,样板间:`builtin-ppt-live`) |
| 幻灯片栅格化 | `app.deck.renderPage` | 在隐藏宿主 WebView 中渲染单页 HTML,返回 base64 PNG/PDF(导出用) |
| 自定义后端 | `app.call('xxx', …)` + `worker.js` | 仅 `node.enabled = true` 时可用,自己实现业务逻辑 |
Expand Down
37 changes: 37 additions & 0 deletions src/apps/desktop/src/api/miniapp_agent_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,23 @@ async fn load_and_validate_miniapp_agent_session(
Ok(Some(session))
}

/// Align a reused hidden session with the tool policy of the current run.
///
/// `enable_tools` is baked into the session config at creation time, so sessions
/// created by older builds (which disabled tools for marketplace MiniApps) would
/// stay tool-less forever. Marketplace runs are now constrained by the backend
/// research allowlist instead, so the session config is repaired on reuse.
async fn sync_agent_session_tool_enablement(
coordinator: &ConversationCoordinator,
session_id: &str,
submission_plan: &MiniAppAgentSubmissionPlan,
) -> Result<(), String> {
coordinator
.update_session_tool_enablement(session_id, submission_plan.enable_tools)
.await
.map_err(|e| format!("Failed to update MiniApp agent session tools: {}", e))
}

/// Ensure that one MiniApp topic has a dedicated hidden Agent session before
/// the user opens its floating chat surface. This command intentionally accepts
/// only an appdata-relative workspace, so it remains a local-host capability
Expand Down Expand Up @@ -298,6 +315,10 @@ pub async fn miniapp_agent_ensure_session(
request.session_id.as_deref(),
&workspace_plan.workspace_path,
request.enable_tools,
state
.miniapp_manager
.uses_market_strict_runtime(&request.app_id)
.await,
);
let requested_model = request
.model
Expand All @@ -324,6 +345,12 @@ pub async fn miniapp_agent_ensure_session(
.await
.map_err(|e| format!("Failed to update MiniApp agent session model: {}", e))?;
}
sync_agent_session_tool_enablement(
coordinator.inner().as_ref(),
&existing_session_id,
&submission_plan,
)
.await?;
(existing_session_id, false)
} else {
check_agent_rate_limit(
Expand Down Expand Up @@ -412,6 +439,10 @@ pub async fn miniapp_agent_run(
request.session_id.as_deref(),
&workspace_path,
request.enable_tools,
state
.miniapp_manager
.uses_market_strict_runtime(&request.app_id)
.await,
);

let requested_model = request
Expand Down Expand Up @@ -439,6 +470,12 @@ pub async fn miniapp_agent_run(
.await
.map_err(|e| format!("Failed to update MiniApp agent session model: {}", e))?;
}
sync_agent_session_tool_enablement(
coordinator.inner().as_ref(),
&existing_session_id,
&submission_plan,
)
.await?;
existing_session_id
} else {
// One hidden session per task keeps MiniApp work isolated and out of
Expand Down
38 changes: 31 additions & 7 deletions src/crates/assembly/core/src/agentic/coordination/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use crate::agentic::skill_agent_snapshot::{
};
use crate::agentic::tools::pipeline::{SubagentParentInfo, ToolPipeline};
use crate::agentic::tools::{
is_miniapp_headless_agent_run, miniapp_headless_agent_tool_restrictions,
miniapp_agent_run_tool_restrictions,
tool_restrictions_for_delegation_policy as runtime_tool_restrictions_for_delegation_policy,
ToolRuntimeRestrictions,
};
Expand Down Expand Up @@ -2117,6 +2117,31 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
Ok(())
}

/// Re-enable (or disable) the tool loop of an already persisted session.
///
/// Session configs are written once at creation, so a host that changes its
/// tool policy would otherwise only affect newly created sessions.
pub async fn update_session_tool_enablement(
&self,
session_id: &str,
enable_tools: bool,
) -> BitFunResult<()> {
self.ensure_session_runtime_ownership(session_id, None)?;

if self
.session_manager
.update_session_tool_enablement(session_id, enable_tools)
.await?
{
info!(
"Coordinator updated session tool enablement: session_id={}, enable_tools={}",
session_id, enable_tools
);
}

Ok(())
}

/// Common creation entry point for normal persisted sessions.
///
/// Delegated subagent sessions use the hidden-subagent creation path instead.
Expand Down Expand Up @@ -4506,14 +4531,13 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
{
skill_agent_context_vars.insert("acp_transport".to_string(), "true".to_string());
}
let runtime_tool_restrictions = if is_miniapp_headless_agent_run(
// Marketplace MiniApps are third-party code, so their hidden agent turns
// run on a read-only research allowlist rather than the wider built-in
// MiniApp tool set.
let runtime_tool_restrictions = miniapp_agent_run_tool_restrictions(
user_message_metadata.as_ref(),
session.created_by.as_deref(),
) {
miniapp_headless_agent_tool_restrictions()
} else {
ToolRuntimeRestrictions::default()
};
);
let runtime_tool_restrictions = runtime_tool_restrictions_for_session_lifetime(
runtime_tool_restrictions,
self.session_manager.is_transient_session(&session_id),
Expand Down
44 changes: 44 additions & 0 deletions src/crates/assembly/core/src/agentic/session/session_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,50 @@ impl SessionManager {
Ok(())
}

/// Update whether a session runs its tool loop.
///
/// `enable_tools` is persisted per session, so a session created while the
/// caller disabled tools stays tool-less forever on reuse. Hosts that later
/// change their mind (for example MiniApp runs that moved from a frontend
/// switch to a backend allowlist) call this to repair existing sessions.
pub async fn update_session_tool_enablement(
&self,
session_id: &str,
enable_tools: bool,
) -> BitFunResult<bool> {
let _mutation_guard = self.acquire_session_mutation(session_id).await?;
if let Some(mut session) = self.sessions.get_mut(session_id) {
if session.config.enable_tools == enable_tools {
return Ok(false);
}
session.config.enable_tools = enable_tools;
session.updated_at = SystemTime::now();
session.last_activity_at = SystemTime::now();
} else {
return Err(BitFunError::NotFound(format!(
"Session not found: {}",
session_id
)));
}

if self.should_persist_session_id(session_id) {
let effective_path = self.effective_session_storage_path(session_id).await;
let session_snapshot = self.sessions.get(session_id).map(|s| s.clone());
if let (Some(workspace_path), Some(session)) = (effective_path, session_snapshot) {
self.persistence_manager
.save_session(&workspace_path, &session)
.await?;
}
}

debug!(
"Session tool enablement updated: session_id={}, enable_tools={}",
session_id, enable_tools
);

Ok(true)
}

/// Inherit parent dialog mode state when creating forked child sessions.
///
/// `last_user_dialog_agent_type` drives first-entry mode reminders, while
Expand Down
7 changes: 4 additions & 3 deletions src/crates/assembly/core/src/agentic/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ pub use registry::{
get_readonly_registered_tool_names, get_readonly_tools,
};
pub use restrictions::{
is_miniapp_headless_agent_run, miniapp_headless_agent_tool_restrictions,
tool_restrictions_for_delegation_policy, ToolPathOperation, ToolPathPolicy,
ToolRuntimeRestrictions,
is_miniapp_headless_agent_run, is_miniapp_market_strict_agent_run,
miniapp_agent_run_tool_restrictions, miniapp_headless_agent_tool_restrictions,
miniapp_market_strict_agent_tool_restrictions, tool_restrictions_for_delegation_policy,
ToolPathOperation, ToolPathPolicy, ToolRuntimeRestrictions,
};
8 changes: 5 additions & 3 deletions src/crates/assembly/core/src/agentic/tools/restrictions.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::util::errors::{BitFunError, BitFunResult};
pub use bitfun_agent_tools::{
is_miniapp_headless_agent_run, is_remote_posix_path_within_root,
miniapp_headless_agent_tool_restrictions, tool_restrictions_for_delegation_policy,
ToolPathOperation, ToolPathPolicy, ToolRestrictionError, ToolRuntimeRestrictions,
is_miniapp_headless_agent_run, is_miniapp_market_strict_agent_run,
is_remote_posix_path_within_root, miniapp_agent_run_tool_restrictions,
miniapp_headless_agent_tool_restrictions, miniapp_market_strict_agent_tool_restrictions,
tool_restrictions_for_delegation_policy, ToolPathOperation, ToolPathPolicy,
ToolRestrictionError, ToolRuntimeRestrictions,
};
use std::path::{Path, PathBuf};

Expand Down
39 changes: 35 additions & 4 deletions src/crates/contracts/product-domains/src/miniapp/agent_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,23 @@ pub fn validate_reused_session(
Ok(())
}

pub fn agent_run_metadata(app_id: &str, run_id: &str) -> serde_json::Value {
json!({
/// Metadata carried by every MiniApp agent turn.
///
/// `market_strict` mirrors the app's runtime profile so the agent runtime can
/// pick the marketplace tool allowlist without reaching back into MiniApp
/// storage. The flag is only serialized for strict apps, keeping the turn
/// metadata of built-in MiniApps byte-identical to earlier releases.
pub fn agent_run_metadata(app_id: &str, run_id: &str, market_strict: bool) -> serde_json::Value {
let mut metadata = json!({
"surface": MINIAPP_AGENT_SURFACE,
"appId": app_id,
"runId": run_id,
"acp_transport": true,
})
});
if market_strict {
metadata["marketStrict"] = Value::Bool(true);
}
metadata
}

pub fn build_agent_submission_plan(
Expand All @@ -300,6 +310,7 @@ pub fn build_agent_submission_plan(
requested_session: Option<&str>,
workspace_path: &str,
enable_tools: Option<bool>,
market_strict: bool,
) -> MiniAppAgentSubmissionPlan {
MiniAppAgentSubmissionPlan {
run_id: run_id.to_string(),
Expand All @@ -308,7 +319,7 @@ pub fn build_agent_submission_plan(
requested_session_id: requested_session_id(requested_session),
workspace_path: workspace_path.to_string(),
enable_tools: enable_tools.unwrap_or(true),
metadata: agent_run_metadata(app_id, run_id),
metadata: agent_run_metadata(app_id, run_id, market_strict),
}
}

Expand Down Expand Up @@ -475,6 +486,7 @@ mod tests {
Some(" session-1 "),
"/workspace",
Some(false),
false,
);
assert_eq!(plan.owner, "miniapp-agent:app-1:run-1");
assert_eq!(plan.session_name, "Session");
Expand All @@ -492,6 +504,25 @@ mod tests {
);
}

#[test]
fn market_strict_runs_tag_their_turn_metadata() {
assert_eq!(
agent_run_metadata("app-1", "run-1", true),
serde_json::json!({
"surface": MINIAPP_AGENT_SURFACE,
"appId": "app-1",
"runId": "run-1",
"acp_transport": true,
"marketStrict": true,
})
);

let plan =
build_agent_submission_plan("app-1", "run-1", None, None, "/workspace", None, true);
assert!(plan.enable_tools);
assert_eq!(plan.metadata["marketStrict"], serde_json::json!(true));
}

#[test]
fn turn_text_starts_after_last_tool_result_for_requested_turn() {
let messages = vec![
Expand Down
Loading
Loading