feat: add MCP session visibility toggle and rebuild release

This commit is contained in:
shiyue
2026-08-26 17:12:45 +08:00
parent f15bc92d1e
commit e62a545569
13 changed files with 564 additions and 7 deletions

View File

@@ -5377,6 +5377,120 @@ function assertMultiAgentV2CompatibilityContract() {
assert(frontendSource.includes('entry.agentPath ? `路径: ${entry.agentPath}`'), 'Sub-agent cards should expose the canonical agent path');
}
function assertMcpSessionVisibilityContract() {
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
const toggleMarkup = indexSource.match(/<button\b[^>]*\bid="mcp-session-visibility-toggle"[^>]*>/)?.[0] || '';
assert(toggleMarkup, 'MCP-created session visibility toggle should exist as a stable sidebar button');
[
'type="button"',
'title=',
'aria-label=',
'aria-pressed="false"',
].forEach((attribute) => {
assert(toggleMarkup.includes(attribute), `MCP-created session visibility toggle should expose ${attribute}`);
});
assert(/\bclass="[^"]*\bicon-only\b[^"]*"/.test(toggleMarkup), 'MCP-created session visibility toggle should be an icon-only button');
const searchRowIndex = indexSource.indexOf('class="session-search-row"');
const toggleIndex = indexSource.indexOf('id="mcp-session-visibility-toggle"');
const sessionListIndex = indexSource.indexOf('id="session-list"');
assert(
searchRowIndex >= 0 && toggleIndex > searchRowIndex && sessionListIndex > toggleIndex,
'MCP-created session visibility toggle should stay in the sidebar search controls before the session list'
);
assert(
/MCP_SESSION_VISIBILITY_STORAGE_KEY\s*=\s*['"][^'"]*mcp[^'"]*session[^'"]*visibility[^'"]*['"]/.test(frontendSource),
'MCP-created session visibility should use a stable local persistence key'
);
assert(
/localStorage\.getItem\(MCP_SESSION_VISIBILITY_STORAGE_KEY\)/.test(frontendSource)
&& /localStorage\.setItem\(MCP_SESSION_VISIBILITY_STORAGE_KEY/.test(frontendSource),
'MCP-created session visibility preference should round-trip through localStorage'
);
const readPreferenceBlock = extractFunctionSource(frontendSource, 'readMcpSessionVisibilityPreference');
const createReadPreference = new Function(
'localStorage',
'MCP_SESSION_VISIBILITY_STORAGE_KEY',
`${readPreferenceBlock}; return readMcpSessionVisibilityPreference;`
);
assert(createReadPreference({ getItem: () => null }, 'test-key')() === true,
'MCP-created sessions should remain visible when no preference has been stored');
assert(createReadPreference({ getItem: () => 'false' }, 'test-key')() === false,
'Stored hidden preference should restore hidden MCP-created sessions');
assert(createReadPreference({ getItem: () => { throw new Error('blocked'); } }, 'test-key')() === true,
'Unavailable localStorage should conservatively keep MCP-created sessions visible');
assert(frontendSource.includes('const mcpSessionVisibilityToggle = $(\'#mcp-session-visibility-toggle\')'),
'Frontend should bind the MCP-created session visibility toggle by its stable id');
assert(
/mcpSessionVisibilityToggle\.setAttribute\('aria-pressed',\s*[^)]*\)/.test(frontendSource),
'MCP-created session visibility toggle should synchronize aria-pressed'
);
const filterBlock = extractFunctionSource(frontendSource, 'sessionPassesMcpVisibilityFilter');
assert(
filterBlock.includes('normalizedSearchQuery')
&& /if\s*\(\s*normalizedSearchQuery\s*\)\s*return true;/.test(filterBlock),
'Non-empty sidebar search should bypass the MCP-created session visibility filter'
);
assert(
/createdFromKind[\s\S]*['"]mcp['"]/.test(filterBlock),
'MCP-created session visibility filter should detect sessions createdFromKind=mcp'
);
assert(
filterBlock.includes('session.id === currentSessionId') && filterBlock.includes('session.isRunning'),
'MCP-created session visibility filter should keep the current session and running sessions visible'
);
const createVisibilityFilter = new Function(
'showMcpSessions',
'currentSessionId',
`${filterBlock}; return sessionPassesMcpVisibilityFilter;`
);
const hiddenFilter = createVisibilityFilter(false, 'mcp-current');
const visibleFilter = createVisibilityFilter(true, 'mcp-current');
const regularSession = { id: 'regular', createdFromKind: 'user', isRunning: false };
const hiddenMcpSession = { id: 'mcp-hidden', createdFromKind: 'mcp', isRunning: false };
const pinnedMcpSession = { ...hiddenMcpSession, id: 'mcp-pinned', pinnedAt: '2026-08-26T00:00:00.000Z' };
const currentMcpSession = { ...hiddenMcpSession, id: 'mcp-current' };
const runningMcpSession = { ...hiddenMcpSession, id: 'mcp-running', isRunning: true };
assert(hiddenFilter(regularSession, '') === true, 'Normal sessions should stay visible while MCP-created sessions are hidden');
assert(hiddenFilter(hiddenMcpSession, '') === false, 'Inactive MCP-created sessions should be hidden');
assert(hiddenFilter(pinnedMcpSession, '') === false, 'Pinned MCP-created sessions should still be hidden when inactive');
assert(hiddenFilter(currentMcpSession, '') === true, 'Current MCP-created session should stay visible');
assert(hiddenFilter(runningMcpSession, '') === true, 'Running MCP-created session should stay visible');
assert(hiddenFilter(hiddenMcpSession, '主动查找') === true, 'Search should temporarily restore matching MCP-created sessions');
assert(visibleFilter(hiddenMcpSession, '') === true, 'Visible preference should keep every MCP-created session visible');
const toggleBindingStart = frontendSource.indexOf('if (mcpSessionVisibilityToggle) {');
const toggleBindingEnd = frontendSource.indexOf('\n }', toggleBindingStart);
const toggleBindingBlock = frontendSource.slice(toggleBindingStart, toggleBindingEnd);
assert(toggleBindingStart >= 0
&& toggleBindingBlock.includes("addEventListener('click'")
&& toggleBindingBlock.includes('persistMcpSessionVisibilityPreference(showMcpSessions)')
&& toggleBindingBlock.includes('renderSessionList()'),
'MCP-created session visibility toggle should persist the new preference and rerender the sidebar');
const renderSessionListBlock = extractFunctionSource(frontendSource, 'renderSessionList');
assert(
renderSessionListBlock.includes('sessionPassesMcpVisibilityFilter(session, normalizedSearchQuery)'),
'Session list rendering should apply the MCP-created session visibility filter with the current search query'
);
const baseToggleStyle = styleSource.match(/\.mcp-session-visibility-toggle\s*\{[^}]*\}/)?.[0] || '';
assert(
/width:\s*34px;/.test(baseToggleStyle)
&& /height:\s*34px;/.test(baseToggleStyle)
&& /flex-basis:\s*34px;/.test(baseToggleStyle),
'Base MCP-created session visibility toggle should keep a stable 34px square footprint'
);
assert(
/html\[data-theme='wasteland'\] \.mcp-session-visibility-toggle\s*\{[^}]*width:\s*36px;[^}]*height:\s*36px;[^}]*flex-basis:\s*36px;/.test(styleSource),
'Wasteland MCP-created session visibility toggle should align to the 36px search row controls'
);
}
function assertAdvancedSessionSearchContract() {
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
@@ -6352,6 +6466,11 @@ async function main() {
console.log('Advanced session search regression checks passed.');
return;
}
if (regressionTarget === 'mcp-session-visibility') {
assertMcpSessionVisibilityContract();
console.log('MCP session visibility regression checks passed.');
return;
}
if (regressionTarget === 'session-preview-metadata') {
assertCcwebListConversationsScopeContract();
await runSessionPreviewMetadataRegression();
@@ -6425,6 +6544,7 @@ async function main() {
assertSessionSwitchResilienceContract();
assertCodexAppGoalLifecycleContract();
assertSessionSwitchRaceContract();
assertMcpSessionVisibilityContract();
assertAdvancedSessionSearchContract();
await assertAdvancedSearchTimeOrderingContract();
assertUsageStatisticsUnitChecks();