feat(page-ai): add resumable run journal descriptors

This commit is contained in:
lix-2026
2026-06-09 18:48:46 +08:00
parent 6ef233772e
commit 922965d30f
17 changed files with 3254 additions and 58 deletions
@@ -6,6 +6,7 @@ export function createSidebarPageAiRenderRuntime(context) {
documentRef,
escapeHtml,
localMarkdownRelativePathFromPageAiDocumentId,
pageAiAgentDescriptor,
pageAiAgentRecord,
pageAiAllSkillEntries,
pageAiBuildAllowedRoots,
@@ -18,6 +19,8 @@ export function createSidebarPageAiRenderRuntime(context) {
pageAiCurrentSkillSource,
pageAiCurrentSkillSourceLabel,
pageAiDefaultChatOnlyProfileSpec,
pageAiDescriptorFieldOptions,
pageAiDescriptorFieldValue,
pageAiEditorTargetCandidates,
pageAiEnsureContextRefState,
pageAiFilteredHistoryRows,
@@ -125,7 +128,9 @@ export function createSidebarPageAiRenderRuntime(context) {
function pageAiCurrentAgentSelectionLabel() {
var agentId = pageAiCurrentAgentId();
if (agentId === 'reasonix') return 'Reasonix';
var descriptor = pageAiAgentDescriptor(agentId);
var descriptorLabel = String(descriptor && (descriptor.displayName || descriptor.label) || '').trim();
if (agentId === 'reasonix') return descriptorLabel || 'Reasonix';
var profile = pageAiCurrentProfileRecord();
var chatOnlySpec = pageAiChatOnlyProfileSpec(profile || pageAiCurrentProfile());
if (agentId === 'chat_only') {
@@ -134,8 +139,8 @@ export function createSidebarPageAiRenderRuntime(context) {
if (chatOnlySpec) {
return 'ChatOnly / ' + chatOnlySpec.label;
}
if (agentId === 'hermes') return 'Hermes / ' + pageAiProfileDisplayLabel(profile, pageAiCurrentProfile());
return pageAiAgentRecord(agentId).label;
if (agentId === 'hermes') return (descriptorLabel || 'Hermes') + ' / ' + pageAiProfileDisplayLabel(profile, pageAiCurrentProfile());
return descriptorLabel || pageAiAgentRecord(agentId).label;
}
function pageAiRenderAgentProfileOption(agentId, profile, label, detail) {
@@ -148,6 +153,59 @@ export function createSidebarPageAiRenderRuntime(context) {
'</button>';
}
function pageAiAgentDescriptorDetail(descriptor) {
if (!descriptor) return '';
var capabilities = pageAiNormalizeArray(descriptor.capabilities);
var toolCount = Number(descriptor.toolCount || pageAiNormalizeArray(descriptor.tools).length || 0);
var parts = [];
parts.push(descriptor.canWriteFiles === false ? '只读/聊天' : '可写授权文件');
if (capabilities.length) parts.push('capability ' + capabilities.length);
if (toolCount) parts.push('tool ' + toolCount);
return parts.join(' · ');
}
function pageAiRenderDescriptorField(field) {
var key = String(field && field.key || '').trim();
if (!key || field.secret === true) return '';
var label = String(field.label || key).trim();
var kind = String(field.kind || 'string').trim();
var value = pageAiDescriptorFieldValue(field);
var options = pageAiDescriptorFieldOptions(field);
var control = '';
if (kind === 'select' || options.length) {
control = '<select data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="select">' +
options.map(function(option) {
return '<option value="' + escapeHtml(option.value) + '"' + (String(value) === String(option.value) ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
}).join('') +
'</select>';
} else if (kind === 'boolean' || kind === 'toggle') {
control = '<input type="checkbox" data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="boolean"' + (value === true ? ' checked' : '') + ' />';
} else {
control = '<input type="text" value="' + escapeHtml(value == null ? '' : String(value)) + '" data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="' + escapeHtml(kind || 'string') + '" />';
}
return '' +
'<label class="wolai-page-ai-profile-select" data-page-ai-descriptor-field-row="' + escapeHtml(key) + '">' +
'<span>' + escapeHtml(label) + '</span>' +
control +
'</label>';
}
function pageAiRenderAgentDescriptorCard(agentId, fallbackTitle) {
var descriptor = pageAiAgentDescriptor(agentId) || pageAiAgentRecord(agentId);
var title = String(descriptor && (descriptor.displayName || descriptor.label) || fallbackTitle || agentId).trim();
var fields = pageAiNormalizeArray(descriptor && descriptor.fields).map(pageAiRenderDescriptorField).filter(Boolean).join('');
var disabled = pageAiNormalizeArray(descriptor && descriptor.disabledCapabilities);
var disabledText = disabled.length ? '<div class="wolai-page-ai-tool-meta">disabled: ' + escapeHtml(disabled.join(', ')) + '</div>' : '';
return '' +
'<section class="wolai-page-ai-memory-card" data-page-ai-agent-descriptor-card="' + escapeHtml(agentId) + '">' +
'<div class="wolai-page-ai-memory-head">' +
'<div><div class="wolai-page-ai-memory-title">' + escapeHtml(title) + '</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(pageAiAgentDescriptorDetail(descriptor) || 'descriptor fallback') + '</div></div>' +
'</div>' +
disabledText +
(fields ? '<div class="wolai-page-ai-settings-grid">' + fields + '</div>' : '<div class="wolai-page-ai-tool-meta">当前 agent 没有可配置字段。</div>') +
'</section>';
}
function pageAiCurrentModelLabel() {
var profile = pageAiCurrentProfileRecord();
var toolModel = pageAiMnoteToolModel();
@@ -277,6 +335,9 @@ export function createSidebarPageAiRenderRuntime(context) {
return pageAiRenderAgentProfileOption('hermes', profile, pageAiProfileDisplayLabel(profile, profileId), scope);
}).join('');
var reasonixActive = activeAgentId === 'reasonix';
var reasonixDescriptor = pageAiAgentDescriptor('reasonix');
var reasonixLabel = String(reasonixDescriptor && (reasonixDescriptor.displayName || reasonixDescriptor.label) || 'Reasonix').trim() || 'Reasonix';
var reasonixDetail = pageAiAgentDescriptorDetail(reasonixDescriptor) || '可读取并在授权目录内写文件';
agentPopover.innerHTML = '' +
'<div class="wolai-page-ai-context-popover-head">' +
'<strong>选择 Agent</strong>' +
@@ -294,8 +355,8 @@ export function createSidebarPageAiRenderRuntime(context) {
'<div class="wolai-page-ai-agent-popover-title">Reasonix</div>' +
'<div class="wolai-page-ai-agent-option-list">' +
'<button type="button" class="wolai-page-ai-agent-option' + (reasonixActive ? ' is-active' : '') + '" data-page-ai-agent-id="reasonix" aria-pressed="' + (reasonixActive ? 'true' : 'false') + '">' +
'<span class="wolai-page-ai-agent-option-label">Reasonix</span>' +
'<span class="wolai-page-ai-agent-option-detail">可读取并在授权目录内写文件</span>' +
'<span class="wolai-page-ai-agent-option-label">' + escapeHtml(reasonixLabel) + '</span>' +
'<span class="wolai-page-ai-agent-option-detail">' + escapeHtml(reasonixDetail) + '</span>' +
'</button>' +
'</div>' +
'</div>';
@@ -414,9 +475,11 @@ export function createSidebarPageAiRenderRuntime(context) {
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
if (agentPanel instanceof HTMLElement) {
agentPanel.innerHTML = '' +
(pageUiState.pageAiAgentDescriptorError ? '<div class="wolai-page-ai-inline-error">' + escapeHtml(pageUiState.pageAiAgentDescriptorError) + '</div>' : '') +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">授权区域</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(pageAiBuildAllowedRoots().length ? 'SQLite directory_grants' : '需要授权') + '</div></div>' +
'</section>' +
pageAiRenderAgentDescriptorCard(activeAgentId, pageAiCurrentAgentSelectionLabel()) +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">默认上下文</div><div class="wolai-page-ai-memory-scope">ContextRefs</div></div>' +
'</section>' +
@@ -512,7 +575,7 @@ export function createSidebarPageAiRenderRuntime(context) {
}
var hermesMemoryPanel = drawer.querySelector('[data-page-ai-hermes-panel]');
if (hermesMemoryPanel instanceof HTMLElement) {
hermesMemoryPanel.innerHTML = ['soul', 'user', 'memory'].map(function(section) {
hermesMemoryPanel.innerHTML = pageAiRenderAgentDescriptorCard('hermes', 'Hermes') + ['soul', 'user', 'memory'].map(function(section) {
var label = pageAiMemoryFileLabel(section);
var value = pageUiState.pageAiProfileMemoryDrafts[section] || '';
return '' +
@@ -713,6 +776,7 @@ export function createSidebarPageAiRenderRuntime(context) {
var reasonixPanel = drawer.querySelector('[data-page-ai-reasonix-panel]');
if (reasonixPanel instanceof HTMLElement) {
reasonixPanel.innerHTML = '' +
pageAiRenderAgentDescriptorCard('reasonix', 'Reasonix') +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head">' +
'<div><div class="wolai-page-ai-memory-title">Reasonix 专属设置</div><div class="wolai-page-ai-memory-scope">ACP runtime / memory</div></div>' +
@@ -723,7 +787,8 @@ export function createSidebarPageAiRenderRuntime(context) {
}
var chatOnlyPanel = drawer.querySelector('[data-page-ai-chat-only-panel]');
if (chatOnlyPanel instanceof HTMLElement) {
chatOnlyPanel.innerHTML = '<section class="wolai-page-ai-memory-card"><div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">Chat-only</div><div class="wolai-page-ai-memory-scope">默认不申请文件写权限</div></div></section>';
chatOnlyPanel.innerHTML = pageAiRenderAgentDescriptorCard('chat_only', 'Chat-only') +
'<section class="wolai-page-ai-memory-card"><div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">Chat-only</div><div class="wolai-page-ai-memory-scope">默认不申请文件写权限</div></div></section>';
}
}