Add operation approval policy editor
This commit is contained in:
@@ -572,6 +572,27 @@ var TRANSLATIONS = {
|
||||
'wizard.step5.execution': 'Execution settings',
|
||||
'wizard.step5.exec_title': 'Request execution',
|
||||
'wizard.step5.exec_subtitle': 'Timeout, retry count and authorization profile',
|
||||
'wizard.approval.title': 'Human confirmation',
|
||||
'wizard.approval.subtitle': 'Enable this for actions that must not run without an explicit user decision.',
|
||||
'wizard.approval.required_label': 'Require confirmation before execution',
|
||||
'wizard.approval.required_desc': 'The MCP client receives a pending request, and the action runs only after confirmation through a separate approval endpoint.',
|
||||
'wizard.approval.risk_level': 'Risk level',
|
||||
'wizard.approval.risk.normal': 'Normal action',
|
||||
'wizard.approval.risk.dangerous': 'Dangerous action',
|
||||
'wizard.approval.risk.financial': 'Financial action',
|
||||
'wizard.approval.risk.irreversible': 'Irreversible action',
|
||||
'wizard.approval.ttl': 'How long to wait for confirmation',
|
||||
'wizard.approval.confirmation_title': 'Confirmation title',
|
||||
'wizard.approval.confirmation_title_hint': 'This text will be shown by the external confirmation interface.',
|
||||
'wizard.approval.confirmation_body': 'Description for the user',
|
||||
'wizard.approval.confirmation_body_hint': 'Explain briefly what will happen after confirmation.',
|
||||
'wizard.approval.show_payload': 'Show request parameters',
|
||||
'wizard.approval.payload_mode': 'How to show parameters',
|
||||
'wizard.approval.payload.summary': 'Short summary',
|
||||
'wizard.approval.payload.masked_json': 'JSON with masked secrets',
|
||||
'wizard.approval.preview_label': 'Preview for confirmation UI',
|
||||
'wizard.approval.default_title': 'Confirm operation execution',
|
||||
'wizard.approval.default_body': 'Review operation parameters and confirm execution.',
|
||||
'wizard.step5.security_level_title': 'Operation security',
|
||||
'wizard.step5.community_security_note': '',
|
||||
'wizard.step5.live_title': 'Check and publish',
|
||||
@@ -1396,6 +1417,27 @@ var TRANSLATIONS = {
|
||||
'wizard.step5.execution': 'Параметры выполнения',
|
||||
'wizard.step5.exec_title': 'Выполнение запроса',
|
||||
'wizard.step5.exec_subtitle': 'Время ожидания, повторные попытки и профиль авторизации',
|
||||
'wizard.approval.title': 'Подтверждение человеком',
|
||||
'wizard.approval.subtitle': 'Включайте для действий, которые нельзя выполнять без явного решения пользователя.',
|
||||
'wizard.approval.required_label': 'Требовать подтверждение перед выполнением',
|
||||
'wizard.approval.required_desc': 'MCP клиент получит ожидающий запрос, а действие выполнится только после подтверждения через отдельный эндпоинт подтверждения.',
|
||||
'wizard.approval.risk_level': 'Уровень риска',
|
||||
'wizard.approval.risk.normal': 'Обычное действие',
|
||||
'wizard.approval.risk.dangerous': 'Опасное действие',
|
||||
'wizard.approval.risk.financial': 'Финансовое действие',
|
||||
'wizard.approval.risk.irreversible': 'Необратимое действие',
|
||||
'wizard.approval.ttl': 'Сколько ждать подтверждение',
|
||||
'wizard.approval.confirmation_title': 'Заголовок подтверждения',
|
||||
'wizard.approval.confirmation_title_hint': 'Этот текст увидит внешний интерфейс подтверждения.',
|
||||
'wizard.approval.confirmation_body': 'Описание для пользователя',
|
||||
'wizard.approval.confirmation_body_hint': 'Коротко объясните, что произойдет после подтверждения.',
|
||||
'wizard.approval.show_payload': 'Показывать параметры запроса',
|
||||
'wizard.approval.payload_mode': 'Как показывать параметры',
|
||||
'wizard.approval.payload.summary': 'Краткое описание',
|
||||
'wizard.approval.payload.masked_json': 'JSON с маскированием секретов',
|
||||
'wizard.approval.preview_label': 'Предпросмотр для интерфейса подтверждения',
|
||||
'wizard.approval.default_title': 'Подтвердите выполнение операции',
|
||||
'wizard.approval.default_body': 'Проверьте параметры операции и подтвердите выполнение.',
|
||||
'wizard.step5.security_level_title': 'Защита операции',
|
||||
'wizard.step5.community_security_note': '',
|
||||
'wizard.step5.live_title': 'Проверка и публикация',
|
||||
|
||||
@@ -27,6 +27,45 @@ function buildWizardState() {
|
||||
};
|
||||
}
|
||||
|
||||
function checkedValue(id) {
|
||||
var element = document.getElementById(id);
|
||||
return !!(element && element.checked);
|
||||
}
|
||||
|
||||
function normalizeApprovalTtlSeconds(value) {
|
||||
var ttl = Number(value || 300);
|
||||
if (!Number.isFinite(ttl)) return 300;
|
||||
return Math.max(1, Math.min(300, Math.round(ttl)));
|
||||
}
|
||||
|
||||
function buildApprovalPolicy() {
|
||||
if (!checkedValue('approval-required')) return null;
|
||||
|
||||
var title = textValue('approval-title') || tKey('wizard.approval.default_title');
|
||||
var body = textValue('approval-body') || tKey('wizard.approval.default_body');
|
||||
|
||||
return {
|
||||
required: true,
|
||||
risk_level: textValue('approval-risk-level') || 'normal',
|
||||
confirmation_title: title,
|
||||
confirmation_body_template: body,
|
||||
ttl_seconds: normalizeApprovalTtlSeconds(textValue('approval-ttl-seconds')),
|
||||
show_payload_preview: checkedValue('approval-show-payload-preview'),
|
||||
payload_preview_mode: textValue('approval-payload-preview-mode') || 'summary',
|
||||
};
|
||||
}
|
||||
|
||||
function applyApprovalPolicyToExecutionConfig(config) {
|
||||
var next = config || {};
|
||||
var policy = buildApprovalPolicy();
|
||||
if (policy) {
|
||||
next.approval_policy = policy;
|
||||
} else {
|
||||
next.approval_policy = null;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function collectWizardPayload() {
|
||||
var name = textValue('tool-name');
|
||||
if (!name) throw new Error(tKey('wizard.error.tool_name'));
|
||||
@@ -50,7 +89,7 @@ function collectWizardPayload() {
|
||||
output_schema: convertJsonSchemaToCrankSchema(outputSchemaValue, []),
|
||||
input_mapping: buildMappingSet(inputMappingValue, 'input'),
|
||||
output_mapping: buildMappingSet(outputMappingValue, 'output'),
|
||||
execution_config: parseExecutionConfig(textValue('tool-exec-config')),
|
||||
execution_config: applyApprovalPolicyToExecutionConfig(parseExecutionConfig(textValue('tool-exec-config'))),
|
||||
tool_description: buildToolDescription(),
|
||||
wizard_state: buildWizardState(),
|
||||
};
|
||||
@@ -126,6 +165,7 @@ function bindWizardLiveActions() {
|
||||
if (window.CrankWizardMapping && typeof window.CrankWizardMapping.initialize === 'function') {
|
||||
window.CrankWizardMapping.initialize();
|
||||
}
|
||||
bindApprovalPolicyControls();
|
||||
bindAgentFacingPreview();
|
||||
}
|
||||
|
||||
@@ -147,6 +187,54 @@ function bindLiveAction(id, busyLabel, handler) {
|
||||
});
|
||||
}
|
||||
|
||||
function setApprovalPolicyEditor(policy) {
|
||||
var enabled = !!(policy && policy.required);
|
||||
var required = document.getElementById('approval-required');
|
||||
if (required) required.checked = enabled;
|
||||
setValue('approval-risk-level', policy && policy.risk_level ? policy.risk_level : 'normal');
|
||||
setValue('approval-ttl-seconds', policy && policy.ttl_seconds ? String(policy.ttl_seconds) : '300');
|
||||
setValue('approval-title', policy && policy.confirmation_title ? policy.confirmation_title : tKey('wizard.approval.default_title'));
|
||||
setValue('approval-body', policy && policy.confirmation_body_template ? policy.confirmation_body_template : tKey('wizard.approval.default_body'));
|
||||
var showPayload = document.getElementById('approval-show-payload-preview');
|
||||
if (showPayload) {
|
||||
showPayload.checked = !policy || policy.show_payload_preview !== false;
|
||||
}
|
||||
setValue('approval-payload-preview-mode', policy && policy.payload_preview_mode ? policy.payload_preview_mode : 'summary');
|
||||
updateApprovalPolicyUi();
|
||||
}
|
||||
|
||||
function updateApprovalPolicyUi() {
|
||||
var enabled = checkedValue('approval-required');
|
||||
var toggle = document.getElementById('approval-required-toggle');
|
||||
var fields = document.getElementById('approval-config-fields');
|
||||
if (toggle) toggle.classList.toggle('on', enabled);
|
||||
if (fields) fields.hidden = !enabled;
|
||||
|
||||
var title = textValue('approval-title') || tKey('wizard.approval.default_title');
|
||||
var body = textValue('approval-body') || tKey('wizard.approval.default_body');
|
||||
setTextContent('approval-preview-title', title);
|
||||
setTextContent('approval-preview-body', body);
|
||||
}
|
||||
|
||||
function bindApprovalPolicyControls() {
|
||||
[
|
||||
'approval-required',
|
||||
'approval-risk-level',
|
||||
'approval-ttl-seconds',
|
||||
'approval-title',
|
||||
'approval-body',
|
||||
'approval-show-payload-preview',
|
||||
'approval-payload-preview-mode',
|
||||
].forEach(function(id) {
|
||||
var element = document.getElementById(id);
|
||||
if (!element || element.dataset.approvalBound === 'true') return;
|
||||
element.dataset.approvalBound = 'true';
|
||||
element.addEventListener('input', updateApprovalPolicyUi);
|
||||
element.addEventListener('change', updateApprovalPolicyUi);
|
||||
});
|
||||
updateApprovalPolicyUi();
|
||||
}
|
||||
|
||||
async function runWizardLiveAction(button, busyLabel, handler) {
|
||||
if (!button || button.dataset.busy === 'true') {
|
||||
return;
|
||||
@@ -708,4 +796,5 @@ function copyTestResponseToOutputSample() {
|
||||
bindWizardLiveActions: bindWizardLiveActions,
|
||||
updateWizardProtocolVisibility: updateWizardProtocolVisibility,
|
||||
renderAgentFacingPreview: renderAgentFacingPreview,
|
||||
setApprovalPolicyEditor: setApprovalPolicyEditor,
|
||||
};
|
||||
|
||||
@@ -429,6 +429,13 @@ function executionConfigToEditorValue(config) {
|
||||
return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
function setApprovalPolicyFromSnapshot(config) {
|
||||
if (!window.CrankWizardLive || typeof window.CrankWizardLive.setApprovalPolicyEditor !== 'function') {
|
||||
return;
|
||||
}
|
||||
window.CrankWizardLive.setApprovalPolicyEditor(config && config.approval_policy ? config.approval_policy : null);
|
||||
}
|
||||
|
||||
function operationSnapshot(versionDocument) {
|
||||
if (!versionDocument) return {};
|
||||
return versionDocument.snapshot || versionDocument;
|
||||
@@ -487,6 +494,7 @@ function prefillWizardFromEdit(detail, versionDocument) {
|
||||
setValue('tool-input-mapping', mappingSetToEditorValue(snapshot.input_mapping, 'input', snapshot.protocol || detail.protocol));
|
||||
setValue('tool-output-mapping', mappingSetToEditorValue(snapshot.output_mapping, 'output', snapshot.protocol || detail.protocol));
|
||||
setValue('tool-exec-config', executionConfigToEditorValue(snapshot.execution_config || {}));
|
||||
setApprovalPolicyFromSnapshot(snapshot.execution_config || {});
|
||||
prefillWizardSamples(snapshot);
|
||||
if (window.CrankWizardMapping && typeof window.CrankWizardMapping.renderFromEditors === 'function') {
|
||||
window.CrankWizardMapping.renderFromEditors();
|
||||
|
||||
Reference in New Issue
Block a user