chore: rebuild release package and commit updates

This commit is contained in:
shiyue
2026-07-13 10:13:04 +08:00
parent dd466a69b5
commit 141a266f34
19 changed files with 1066 additions and 35 deletions

View File

@@ -647,6 +647,108 @@ function assertFrontendMcpReloadContract() {
assert(source.includes('MCP 启动失败'), 'Frontend should expose a failed startup toast');
}
function assertFrontendSubagentCardMetadataContract() {
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
const metadataStart = source.indexOf(' function cleanCollabAgentText(value)');
const metadataEnd = source.indexOf(' function normalizeCollabAgentAction(value)', metadataStart);
assert(metadataStart >= 0 && metadataEnd > metadataStart, 'Frontend should expose an isolated sub-agent metadata helper block');
const metadataApi = new Function(`
function shortChildAgentId(id) {
const value = String(id || '');
return value.length > 12 ? value.slice(0, 8) : value;
}
${source.slice(metadataStart, metadataEnd)}
return {
isGenericCollabAgentLabel,
pickCollabAgentTitle,
mergeCollabAgentTaskState,
};
`)();
assert(source.includes('function pickCollabAgentTitle(state, id, index)'), 'Frontend should pick sub-agent titles through a dedicated helper');
assert(
/const titleCandidates = \[\s*state\.label,\s*state\.title,\s*state\.nickname,\s*state\.name,\s*\]/.test(source),
'Sub-agent title priority should be label -> title -> nickname -> name'
);
assert(source.includes('isGenericCollabAgentLabel(candidate, id)'), 'Sub-agent title picker should skip generic labels and thread IDs');
assert(source.includes('collabAgentTitleFromPrompt(taskDescription)'), 'Generic sub-agent titles should be derived from that agent prompt');
assert(source.includes('return id ? `ID ${shortChildAgentId(id)}`'), 'Sub-agent title picker should fall back to a short thread id without a prompt');
assert(source.includes('function mergeCollabAgentTaskState(previousState = {}, incomingState = {}'), 'Frontend should centralize per-agent task metadata merging');
assert(source.includes('hasReadableSourceTitle: hasReadableCollabAgentTitle(state, id)'), 'Normalized child states should preserve whether a title came from protocol fields');
assert(/mergeCollabAgentTaskState\(\s*states\[entry\.id\]/.test(source), 'Structured child states should use the tested metadata merge helper');
assert(/mergeCollabAgentTaskState\(\s*states\[id\]/.test(source), 'Receiver-only child states should use the tested metadata merge helper');
assert(source.includes('entry.detail ? `结果: ${entry.detail}` :'), 'Card title should keep runtime result in the container title');
assert(source.includes("description.className = 'collab-agent-item-description'"), 'Sub-agent cards should render a visible task intro node');
assert(source.includes('description.title = entry.taskDescription'), 'Task intro node should expose the full task intro in its title attribute');
assert(source.includes('label.textContent = pickCollabAgentTitle(entry, entry.id, index);'), 'Rendered card label should use normalized title selection');
assert(/\.collab-agent-item-description\s*\{[\s\S]*?-webkit-line-clamp:\s*2;/.test(styleSource), 'Sub-agent task intro should use two-line truncation');
assert(/\.collab-agent-item\s*\{[\s\S]*?min-width:\s*0;[\s\S]*?flex-direction:\s*column;/.test(styleSource), 'Sub-agent cards should be vertically composed and flex-shrink on narrow screens');
assert(/@media \(max-width:\s*640px\)[\s\S]*?\.collab-agent-item\s*\{[\s\S]*?min-width:\s*0;[\s\S]*?\}/.test(styleSource), 'Narrow screens should let sub-agent cards shrink without horizontal overflow');
const uuidV7 = '0190f01d-7b3e-7f03-9a5a-123456789abc';
assert(metadataApi.isGenericCollabAgentLabel(uuidV7, 'different-thread-id'), 'UUID v7 labels should be treated as thread identifiers');
assert(
metadataApi.pickCollabAgentTitle({ label: '子代理', title: '架构审查', name: uuidV7 }, 'child-a', 0) === '架构审查',
'Title selection should skip generic labels and preserve readable field priority'
);
const firstSpawn = metadataApi.mergeCollabAgentTaskState(
{},
{ label: '子代理', status: 'running' },
'请审查前端实现。核对标题和简介。',
'child-thread-a',
0
);
const secondSpawn = metadataApi.mergeCollabAgentTaskState(
{},
{ label: '子代理', status: 'running' },
'请验证后端回归。核对状态同步。',
'child-thread-b',
1
);
assert(firstSpawn.taskDescription !== secondSpawn.taskDescription, 'Independent spawns should retain different task introductions');
assert(firstSpawn.label !== secondSpawn.label, 'Independent spawns should derive different titles from their own prompts');
const afterWait = metadataApi.mergeCollabAgentTaskState(firstSpawn, { status: 'completed' }, '', 'child-thread-a', 0);
const afterClose = metadataApi.mergeCollabAgentTaskState(afterWait, { status: 'closed' }, '', 'child-thread-a', 0);
assert(afterWait.taskDescription === firstSpawn.taskDescription, 'Wait updates without a prompt should preserve the original task introduction');
assert(afterClose.taskDescription === firstSpawn.taskDescription, 'Close updates without a prompt should preserve the original task introduction');
const namedSpawn = metadataApi.mergeCollabAgentTaskState({}, { name: '实现代理', status: 'running' }, '', 'child-thread-c', 2);
const namedAfterWait = metadataApi.mergeCollabAgentTaskState(
namedSpawn,
{ label: 'ID child-th', title: '', nickname: '', name: '', status: 'completed' },
'',
'child-thread-c',
2
);
assert(namedAfterWait.label === '实现代理', 'Status updates without a readable title should preserve the existing protocol title');
const namedAfterPromptDerivedUpdate = metadataApi.mergeCollabAgentTaskState(
namedSpawn,
{
label: '整理前端改动并回报状态',
name: 'child-thread-c',
taskDescription: '请整理前端改动并回报状态。',
hasReadableSourceTitle: false,
status: 'completed',
},
'',
'child-thread-c',
2
);
assert(namedAfterPromptDerivedUpdate.label === '实现代理', 'Prompt-derived labels should not replace an existing protocol title');
assert(
namedAfterPromptDerivedUpdate.taskDescription === '请整理前端改动并回报状态。',
'Prompt-derived updates may refresh the task introduction while preserving the protocol title'
);
const noPrompt = metadataApi.mergeCollabAgentTaskState({}, { label: '子代理' }, '', uuidV7, 0);
assert(/^ID\s/.test(noPrompt.label) && noPrompt.label !== uuidV7, 'Missing prompts should fall back to a short thread id');
}
function assertFrontendPrimaryCodexAppUiContract() {
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
@@ -812,7 +914,88 @@ function assertSessionSwitchResilienceContract() {
);
}
function extractFunctionSource(source, name) {
const start = source.indexOf(`function ${name}(`);
assert(start >= 0, `Server should define ${name}`);
let parenDepth = 0;
let signatureEnd = -1;
for (let i = start; i < source.length; i += 1) {
const ch = source[i];
if (ch === '(') parenDepth += 1;
if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) {
signatureEnd = i;
break;
}
}
}
assert(signatureEnd > start, `Server function ${name} should have a complete signature`);
const open = source.indexOf('{', signatureEnd);
assert(open > start, `Server function ${name} should have a body`);
let depth = 0;
for (let i = open; i < source.length; i += 1) {
const ch = source[i];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) return source.slice(start, i + 1);
}
}
throw new Error(`Could not parse function body for ${name}`);
}
function assertCodexAppUnroutedNotificationRoutingContract() {
const source = fs.readFileSync(SERVER_PATH, 'utf8');
assert(source.includes('const codexAppThreadSessionIndex = new Map();'), 'Server should keep an O(1) Codex App thread -> session index');
assert(source.includes('const codexAppUnknownThreadMisses = new Map();'), 'Server should keep a bounded negative cache for unknown Codex App threads');
assert(source.includes('const codexAppUnroutedNotificationLogTimes = new Map();'), 'Server should throttle unrouted notification logs by thread/method');
assert(source.includes('function recoverCcwebMcpChildThreadsFromPersistedToolCalls'), 'Server should restore child-thread routes from persisted collaboration tool calls');
assert(
/recoverCcwebMcpChildThreadsFromPersistedToolCalls\(\s*sessionId,\s*state,\s*toolCalls\s*\)/.test(source),
'Codex App recovery should rebuild persisted child-thread routes before cleaning run state'
);
assert(source.includes('function updateSessionRuntimeThreadIndex'), 'Server should centralize session thread index maintenance');
assert(
/function saveSession\(session\)[\s\S]*?updateSessionRuntimeThreadIndex\(session\)/.test(source),
'Saving a session should refresh the runtime thread index'
);
assert(
/function handleDeleteSession\(ws, sessionId\)[\s\S]*?removeSessionRuntimeThreadIndex\(sessionId\)/.test(source),
'Deleting a session should remove its runtime thread index entry'
);
const lookupBlock = extractFunctionSource(source, 'findCodexAppSessionByThreadId');
assert(lookupBlock.includes('codexAppThreadSessionIndex.get(targetThreadId)'), 'Thread lookup should consult the O(1) index');
assert(lookupBlock.includes('codexAppUnknownThreadMisses'), 'Thread lookup should use the unknown-thread negative cache');
assert(!lookupBlock.includes('fs.readdirSync(SESSIONS_DIR)'), 'Thread lookup must not synchronously scan all session files on the notification hot path');
const routeBlock = extractFunctionSource(source, 'findCodexAppRouteByRuntime');
const childIndex = routeBlock.indexOf("role: 'child'");
const adoptIndex = routeBlock.indexOf('adoptCodexAppUnroutedTurn');
assert(childIndex >= 0, 'Runtime routing should return child routes');
assert(adoptIndex >= 0, 'Runtime routing should still adopt parent notifications from disk state');
assert(childIndex < adoptIndex, 'Known child threads should route before parent disk adoption');
const notificationBlock = extractFunctionSource(source, 'handleCodexAppNotification');
assert(
notificationBlock.includes('shouldLogCodexAppUnroutedNotification(notification)'),
'Unrouted notification logging should be throttled by a helper'
);
}
async function main() {
const targetIndex = process.argv.indexOf('--target');
const regressionTarget = targetIndex >= 0 ? String(process.argv[targetIndex + 1] || '').trim() : String(process.env.CC_WEB_REGRESSION_TARGET || '').trim();
if (regressionTarget) {
if (regressionTarget !== 'codexapp-unrouted-routing') {
throw new Error(`Unknown regression target: ${regressionTarget}`);
}
assertCodexAppUnroutedNotificationRoutingContract();
console.log('Codex App unrouted routing regression checks passed.');
return;
}
assertFrontendGenerationControlsContract();
assertFrontendComposerMcpContract();
assertFrontendSlashDraftPreservationContract();
@@ -820,6 +1003,7 @@ async function main() {
assertFrontendMarkdownLinkContract();
assertMockCodexAppPromptUserNotTextTriggered();
assertFrontendMcpReloadContract();
assertFrontendSubagentCardMetadataContract();
assertFrontendPrimaryCodexAppUiContract();
assertSetTitleMcpContract();
assertSessionSwitchResilienceContract();