fix local office resource editing

- add local-folder OnlyOffice sign/callback writeback and edit-tab handling

- align main resource tabs, attachment edit menu, slash isolation, and filetree context behavior

- record Sidex/Hermes gap reviews and Reasonix task checklists
This commit is contained in:
lix-2026
2026-05-21 05:40:06 +08:00
parent a29d9868f6
commit eba1010191
50 changed files with 4309 additions and 283 deletions
@@ -1461,13 +1461,22 @@ async fn acp_stream_events(
}
});
let acp_session_id = mgr.create_session(None, None).await.map_err(|e| {
WebError::bad_gateway_code(
"acp_session_create_failed",
format!("ACP session creation failed: {e}"),
)
.with_context(&context)
})?;
// Try to resume stored ACP session, or create a new one.
// Reference: hermes-vscode-main sessionManager.ts ensureSession()
let stored_acp_session_id = payload
.get("acpSessionId")
.and_then(Value::as_str)
.filter(|s| !s.trim().is_empty());
let acp_session_id = mgr
.ensure_session(None, stored_acp_session_id)
.await
.map_err(|e| {
WebError::bad_gateway_code(
"acp_session_ensure_failed",
format!("ACP session ensure failed: {e}"),
)
.with_context(&context)
})?;
let mnote_session_id = session_id_for_run(run_id).unwrap_or_else(|| run_id.to_string());
ACP_ACTIVE_RUNS.lock().expect("acp active runs").insert(
run_id.to_string(),
@@ -1810,6 +1819,82 @@ pub async fn abort_run(
}
}
/// Resolve a pending permission request from an ACP agent.
///
/// POST /api/hermes/client/runs/{run_id}/resolve-permission
/// Body: { "permissionId": "...", "decision": "allow"|"deny" }
///
/// Looks up the active run, forwards the decision to the ACP subprocess,
/// and emits `permission.allowed` / `permission.denied` SSE event.
pub async fn resolve_permission(
Extension(context): Extension<RequestContext>,
Path(run_id): Path<String>,
Json(payload): Json<Value>,
) -> Result<(StatusCode, HeaderMap, Json<Value>), WebError> {
ensure_authenticated(&context)?;
let permission_id = payload
.get("permissionId")
.and_then(Value::as_str)
.filter(|s| !s.trim().is_empty())
.ok_or_else(|| {
WebError::bad_request_code("hermes_client_bad_request", "缺少 permissionId")
.with_context(&context)
})?;
let decision = payload
.get("decision")
.and_then(Value::as_str)
.filter(|s| !s.trim().is_empty())
.ok_or_else(|| {
WebError::bad_request_code("hermes_client_bad_request", "缺少 decision (allow/deny)")
.with_context(&context)
})?;
let profile = profile_for_run(&run_id).unwrap_or_else(|| "default".into());
if acp_runtime_for_run(&run_id, &profile).is_none() {
return Err(WebError::bad_request_code(
"hermes_client_not_acp_run",
format!("run_id={run_id} 不是 ACP run"),
)
.with_context(&context));
}
let active = ACP_ACTIVE_RUNS
.lock()
.expect("acp active runs")
.get(&run_id)
.cloned();
let Some(active) = active else {
return Err(WebError::bad_request_code(
"hermes_client_no_active_run",
format!("run_id={run_id} 没有活跃 ACP run"),
)
.with_context(&context));
};
active
.manager
.resolve_permission(permission_id, decision)
.await
.map_err(|e| {
WebError::bad_request_code(
"hermes_client_permission_resolve_failed",
format!("resolve permission 失败: {e}"),
)
.with_context(&context)
})?;
Ok((
StatusCode::OK,
stamp_client_headers(),
Json(json!({
"ok": true,
"runId": run_id,
"permissionId": permission_id,
"decision": decision,
})),
))
}
pub async fn list_models(
Extension(context): Extension<RequestContext>,
) -> Result<(StatusCode, HeaderMap, Json<Value>), WebError> {