feat: stabilize page AI ACP runtimes
实现并稳定页面 AI 的 ACP Hermes / ACP Reasonix 运行路径。 主要内容: - 分离 profile 与 acpRuntime,ACP Hermes 按所选 Hermes profile 启动并注入 provider key。 - 修复 Reasonix ACP wrapper 的 API key 读取、ToolRegistry 注册、LoopEvent role 映射和 reasoning/final 分流。 - 修复 ACP agent_thought_chunk 被 untagged enum 误解析为 message.delta 的问题,补充 thought 相关单测。 - 补充页面 AI 浏览器验证 skill 证据到 7-15 设计稿,并记录严格验收标准。 - 同步提交当前仓库中已存在的 rust-web / Hermes tools / SSE / bug 文档相关改动。 验证: - node --check scripts/reasonix-acp-wrapper.mjs - cargo test -p mnote-web acp -- --nocapture - 页面 AI ACP 浏览器验证:tmp/page-ai-acp-browser-UAYwyM/
This commit is contained in:
@@ -4117,6 +4117,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return pageAiProfileValue(selected) || 'mnoteai';
|
||||
}
|
||||
|
||||
function pageAiRunProfile() {
|
||||
return String(pageUiState.pageAiAcpRuntime || '').trim() === 'reasonix' ? 'reasonix' : pageAiCurrentProfile();
|
||||
}
|
||||
|
||||
function pageAiMnoteToolModel() {
|
||||
return String(document.documentElement.getAttribute('data-mnote-page-ai-tool-model') || 'deepseek-v4-flash').trim() || 'deepseek-v4-flash';
|
||||
}
|
||||
@@ -4570,7 +4574,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
profile: pageAiCurrentProfile(),
|
||||
profile: pageAiRunProfile(),
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
reason: 'page_ai_user_stop'
|
||||
})
|
||||
});
|
||||
@@ -4610,6 +4615,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var source = String(skill && skill.source || '').trim();
|
||||
if (source === 'hub') return '安装';
|
||||
if (source === 'builtin') return '内置';
|
||||
if (source === 'reasonix') {
|
||||
if (origin === 'project') return 'Reasonix 项目';
|
||||
if (origin === 'global') return 'Reasonix 全局';
|
||||
return 'Reasonix';
|
||||
}
|
||||
return '本地';
|
||||
}
|
||||
|
||||
@@ -4716,7 +4726,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
async function pageAiLoadSkills() {
|
||||
try {
|
||||
var response = await fetch('/api/hermes/client/skills?profile=' + encodeURIComponent(pageAiCurrentProfile()), {
|
||||
var runtime = String(pageUiState.pageAiAcpRuntime || '').trim();
|
||||
var params = runtime === 'reasonix'
|
||||
? 'runtime=reasonix'
|
||||
: 'profile=' + encodeURIComponent(pageAiCurrentProfile());
|
||||
var response = await fetch('/api/hermes/client/skills?' + params, {
|
||||
headers: { 'accept': 'application/json' }
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
@@ -4734,6 +4748,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
async function pageAiToggleSkill(skillName, enabled) {
|
||||
var name = String(skillName || '').trim();
|
||||
if (!name) return;
|
||||
if (String(pageUiState.pageAiAcpRuntime || '').trim() === 'reasonix') return;
|
||||
var previous = null;
|
||||
pageAiSkillListEntries().forEach(function(skill) {
|
||||
if (skill.name === name && previous == null) previous = skill.enabled !== false;
|
||||
@@ -4828,7 +4843,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
function renderPageAiControls() {
|
||||
var drawer = ensurePageAiDrawer();
|
||||
var isAcp = pageUiState.pageAiAcpRuntime !== '';
|
||||
var activeProfile = isAcp ? pageUiState.pageAiAcpRuntime : pageAiCurrentProfile();
|
||||
var activeProfile = pageAiRunProfile();
|
||||
drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat');
|
||||
drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || '');
|
||||
// Populate ACP runtime dropdown
|
||||
@@ -4844,7 +4859,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
// Show/hide Hermes-specific profile select
|
||||
var profileLabel = drawer.querySelector('[data-page-ai-hermes-profile]');
|
||||
if (profileLabel instanceof HTMLElement) {
|
||||
profileLabel.style.display = isAcp ? 'none' : '';
|
||||
profileLabel.style.display = pageUiState.pageAiAcpRuntime === 'reasonix' ? 'none' : '';
|
||||
}
|
||||
// When ACP is selected, populate agent panel with ACP runtime info
|
||||
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
|
||||
@@ -4982,12 +4997,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (skillList instanceof HTMLElement) {
|
||||
var skills = pageAiFilteredSkillEntries();
|
||||
if (!skills.length) {
|
||||
skillList.innerHTML = '<div class="wolai-page-ai-empty">没有匹配的 Hermes skill。</div>';
|
||||
var emptyText = String(pageUiState.pageAiAcpRuntime || '').trim() === 'reasonix'
|
||||
? '没有匹配的 Reasonix skill。'
|
||||
: '没有匹配的 Hermes skill。';
|
||||
skillList.innerHTML = '<div class="wolai-page-ai-empty">' + escapeHtml(emptyText) + '</div>';
|
||||
} else {
|
||||
skillList.innerHTML = skills.map(function(skill) {
|
||||
var sourceText = pageAiSkillOriginLabel(skill) + (skill.modified ? ' · modified' : '');
|
||||
var description = String(skill.description || '').trim();
|
||||
var hasDescription = description && description !== '---' && description !== '无描述';
|
||||
var canToggle = skill.toggleable !== false && String(pageUiState.pageAiAcpRuntime || '').trim() !== 'reasonix';
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-skill-row">' +
|
||||
'<div class="wolai-page-ai-skill-copy">' +
|
||||
@@ -4997,7 +5016,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
'</div>' +
|
||||
(hasDescription ? '<div class="wolai-page-ai-skill-desc">' + escapeHtml(description) + '</div>' : '') +
|
||||
'</div>' +
|
||||
'<button type="button" class="wolai-page-ai-skill-switch' + (skill.enabled !== false ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.name) + '" aria-pressed="' + (skill.enabled !== false ? 'true' : 'false') + '">' +
|
||||
'<button type="button" class="wolai-page-ai-skill-switch' + (skill.enabled !== false ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.name) + '" aria-pressed="' + (skill.enabled !== false ? 'true' : 'false') + '"' + (canToggle ? '' : ' disabled title="Reasonix skills 当前为只读展示"') + '>' +
|
||||
'<span></span>' +
|
||||
'</button>' +
|
||||
'</div>';
|
||||
@@ -5515,7 +5534,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
profile: pageUiState.pageAiAcpRuntime || pageAiCurrentProfile(),
|
||||
profile: pageAiRunProfile(),
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
contextScope: pageUiState.pageAiContextScope,
|
||||
message: prompt,
|
||||
model: pageAiMnoteToolModel(),
|
||||
@@ -6557,7 +6577,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) {
|
||||
var next = String(pageAiAcpRuntimeSelect.value || '').trim();
|
||||
pageUiState.pageAiAcpRuntime = next;
|
||||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||||
pageUiState.pageAiSkillError = '';
|
||||
void pageAiLoadProfiles();
|
||||
void pageAiLoadSkills();
|
||||
renderPageAiControls();
|
||||
renderPageAiProviderButtons();
|
||||
return;
|
||||
@@ -7326,12 +7349,9 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#".tree-row[data-shell-mode="filetree"][data-row-id="' + escapedDocRowId + '"] > .tree-link > .tree-link-title"#
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#"[data-page-title-input="true"][data-document-id="' + escaped + '"]"#
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#".wolai-breadcrumb-current [data-page-title-current]"#
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains(r#"[data-page-title-input="true"][data-document-id="' + escaped + '"]"#));
|
||||
assert!(SIDEBAR_TREE_JS.contains(r#".wolai-breadcrumb-current [data-page-title-current]"#));
|
||||
assert!(!SIDEBAR_TREE_JS.contains(
|
||||
r#".tree-row[data-document-id="' + escaped + '"] > .tree-link > .tree-link-title"#
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user