chore: rebuild release package
This commit is contained in:
@@ -74,12 +74,15 @@ function retryScenarioKey(text, marker) {
|
||||
function collaborationSummary(params = {}) {
|
||||
const collaborationMode = params.collaborationMode;
|
||||
const settings = collaborationMode?.settings || {};
|
||||
const developerInstructions = String(settings.developer_instructions || '');
|
||||
return JSON.stringify({
|
||||
mode: collaborationMode?.mode || null,
|
||||
hasModel: Boolean(settings.model),
|
||||
hasDeveloperInstructions: /Codex sub-agent spawning rules/.test(String(settings.developer_instructions || '')),
|
||||
hasWaitAgentRetryGuidance: /wait_agent[\s\S]*timeout_ms[\s\S]*additional wait_agent rounds/.test(String(settings.developer_instructions || '')),
|
||||
hasDeveloperInstructions: /Codex sub-agent runtime rules/.test(developerInstructions),
|
||||
hasSchemaDrivenSubagents: /current runtime tool schema/.test(developerInstructions),
|
||||
hasLegacyV1Guidance: /fork_context|additional wait_agent rounds/.test(developerInstructions),
|
||||
hasReasoningEffort: Object.prototype.hasOwnProperty.call(settings, 'reasoning_effort'),
|
||||
reasoningEffort: settings.reasoning_effort || null,
|
||||
hasTopLevelModel: Object.prototype.hasOwnProperty.call(params, 'model'),
|
||||
hasTopLevelEffort: Object.prototype.hasOwnProperty.call(params, 'effort'),
|
||||
});
|
||||
@@ -167,6 +170,85 @@ function emitChildCollabTurn(threadId, turnId, finalMessage) {
|
||||
childThread.activeTurnId = null;
|
||||
}
|
||||
|
||||
function emitNestedChildCollabTurn() {
|
||||
const threadId = 'child-thread-v2';
|
||||
const turnId = 'child-turn-v2';
|
||||
const grandchildThreadId = 'grandchild-thread-v2';
|
||||
const childThread = ensureThread(threadId);
|
||||
childThread.activeTurnId = turnId;
|
||||
send({
|
||||
method: 'turn/started',
|
||||
params: {
|
||||
threadId,
|
||||
turn: { id: turnId, status: 'running', items: [] },
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'turn/plan/updated',
|
||||
params: {
|
||||
threadId,
|
||||
turnId,
|
||||
plan: [
|
||||
{ step: '定位子线程路由', status: 'completed' },
|
||||
{ step: '同步父卡片进度', status: 'in_progress' },
|
||||
{ step: '完成集成回归', status: 'pending' },
|
||||
],
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'item/agentMessage/delta',
|
||||
params: {
|
||||
threadId,
|
||||
turnId,
|
||||
itemId: 'child-agent-msg-v2',
|
||||
delta: 'V2 子代理最终消息:父级子代理路由正常。',
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'item/completed',
|
||||
params: {
|
||||
threadId,
|
||||
turnId,
|
||||
completedAtMs: Date.now(),
|
||||
item: {
|
||||
id: 'grandchild-activity-v2',
|
||||
type: 'subAgentActivity',
|
||||
kind: 'started',
|
||||
agentThreadId: grandchildThreadId,
|
||||
agentPath: '/root/v2_parent/v2_grandchild',
|
||||
prompt: '验证 Multi-agent V2 嵌套子代理路由。',
|
||||
},
|
||||
},
|
||||
});
|
||||
emitChildCollabTurn(
|
||||
grandchildThreadId,
|
||||
'grandchild-turn-v2',
|
||||
'V2 孙代理最终消息:嵌套路由正常。'
|
||||
);
|
||||
send({
|
||||
method: 'item/completed',
|
||||
params: {
|
||||
threadId,
|
||||
turnId,
|
||||
completedAtMs: Date.now(),
|
||||
item: {
|
||||
id: 'child-agent-msg-v2',
|
||||
type: 'agentMessage',
|
||||
content: [{ type: 'text', text: 'V2 子代理最终消息:父级子代理路由正常。' }],
|
||||
status: 'completed',
|
||||
},
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'turn/completed',
|
||||
params: {
|
||||
threadId,
|
||||
turn: { id: turnId, status: 'completed', items: [] },
|
||||
},
|
||||
});
|
||||
childThread.activeTurnId = null;
|
||||
}
|
||||
|
||||
function completeTurn(thread, turnId, text, status = 'completed') {
|
||||
if (thread.activeTurnId !== turnId) return;
|
||||
const suffix = thread.steers.length > 0 ? ` | steer: ${thread.steers.join(' | ')}` : '';
|
||||
@@ -267,7 +349,57 @@ function completeTurn(thread, turnId, text, status = 'completed') {
|
||||
});
|
||||
}
|
||||
|
||||
if (/subagent/i.test(text)) {
|
||||
if (/subagent v2/i.test(text)) {
|
||||
send({
|
||||
method: 'item/completed',
|
||||
params: {
|
||||
threadId: thread.id,
|
||||
turnId,
|
||||
completedAtMs: Date.now(),
|
||||
item: {
|
||||
id: 'child-activity-v2',
|
||||
type: 'subAgentActivity',
|
||||
kind: 'started',
|
||||
agentThreadId: 'child-thread-v2',
|
||||
agentPath: '/root/v2_parent',
|
||||
prompt: '验证 Multi-agent V2 子代理路由。',
|
||||
},
|
||||
},
|
||||
});
|
||||
emitNestedChildCollabTurn();
|
||||
send({
|
||||
method: 'item/started',
|
||||
params: {
|
||||
threadId: thread.id,
|
||||
turnId,
|
||||
startedAtMs: Date.now(),
|
||||
item: {
|
||||
id: 'wait-empty-v2',
|
||||
type: 'collabAgentToolCall',
|
||||
tool: 'wait_agent',
|
||||
receiverThreadIds: [],
|
||||
agentsStates: {},
|
||||
status: 'inProgress',
|
||||
},
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'item/completed',
|
||||
params: {
|
||||
threadId: thread.id,
|
||||
turnId,
|
||||
completedAtMs: Date.now(),
|
||||
item: {
|
||||
id: 'wait-empty-v2',
|
||||
type: 'collabAgentToolCall',
|
||||
tool: 'wait_agent',
|
||||
receiverThreadIds: [],
|
||||
agentsStates: {},
|
||||
status: 'completed',
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (/subagent/i.test(text)) {
|
||||
send({
|
||||
method: 'item/started',
|
||||
params: {
|
||||
@@ -331,7 +463,7 @@ function completeTurn(thread, turnId, text, status = 'completed') {
|
||||
emitChildCollabTurn(
|
||||
'child-thread-a',
|
||||
'child-turn-a',
|
||||
'子代理最终消息:结构化渲染和关闭按钮链路已完成。'
|
||||
'子代理最终消息:结构化渲染和中断按钮链路已完成。'
|
||||
);
|
||||
emitChildCollabTurn(
|
||||
'child-thread-b',
|
||||
|
||||
@@ -882,12 +882,12 @@ function assertPlanListProgressContract() {
|
||||
assert(result.progress?.completed === 3 && result.progress?.total === 5, 'Persisted todo result should expose 3/5 progress');
|
||||
|
||||
const summarySource = extractFunctionSource(source, 'applyToolSummary');
|
||||
const progressElementSource = extractFunctionSource(source, 'createPlanProgressElement');
|
||||
const progressElementSource = extractFunctionSource(source, 'createPlanProgressElementFromProgress');
|
||||
assert(summarySource.includes('createPlanProgressElement(tool)'), 'Tool summaries should append the plan progress element beside the title');
|
||||
assert(progressElementSource.includes("meter.setAttribute('role', 'img')"), 'Plan progress should expose an accessible image role');
|
||||
assert(progressElementSource.includes("meter.setAttribute('aria-label', progressLabel)"), 'Plan progress should announce completed and total counts');
|
||||
assert(progressElementSource.includes('Math.min(progress.total, 12)'), 'Long plans should cap visible dots to protect the header layout');
|
||||
assert(progressElementSource.includes("count.textContent = `${progress.completed}/${progress.total}`"), 'Compacted long plans should keep an exact numeric count');
|
||||
assert(progressElementSource.includes('Math.min(normalizedProgress.total, 12)'), 'Long plans should cap visible dots to protect the header layout');
|
||||
assert(progressElementSource.includes("count.textContent = `${normalizedProgress.completed}/${normalizedProgress.total}`"), 'Compacted long plans should keep an exact numeric count');
|
||||
|
||||
assert(styleSource.includes('--plan-progress-complete: var(--success);'), 'Plan progress should inherit the active theme success color');
|
||||
assert(styleSource.includes('--plan-progress-remaining: var(--accent);'), 'Remaining plan progress should inherit the active theme accent color');
|
||||
@@ -1508,9 +1508,15 @@ function assertFrontendSubagentCardMetadataContract() {
|
||||
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('function createPlanProgressElementFromProgress(progress'), 'Frontend should expose reusable plan progress rendering for sub-agent cards');
|
||||
assert(source.includes("plan.className = 'collab-agent-item-plan'"), 'Sub-agent cards should render a compact plan progress row');
|
||||
assert(source.includes("currentStep.className = 'collab-agent-item-plan-current'"), 'Sub-agent cards should render the current plan step');
|
||||
assert(source.includes('description.title = descriptionTitle'), 'Task intro node should expose the full task intro or fallback in its title attribute');
|
||||
assert(source.includes('label.textContent = displayTitle;'), '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-plan\s*\{[\s\S]*?display:\s*flex;/.test(styleSource), 'Sub-agent plan progress should use a compact flex row');
|
||||
assert(/\.collab-agent-item-plan \.plan-progress-dot\s*\{[\s\S]*?width:\s*8px;[\s\S]*?height:\s*8px;[\s\S]*?margin-left:\s*0;/.test(styleSource), 'Sub-agent plan dots should remain compact inside narrow cards');
|
||||
assert(/\.collab-agent-item-plan-current\s*\{[\s\S]*?text-overflow:\s*ellipsis;/.test(styleSource), 'Sub-agent current plan step should truncate safely');
|
||||
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');
|
||||
|
||||
@@ -1666,6 +1672,33 @@ function assertFrontendSubagentCardMetadataContract() {
|
||||
assert(spawnedMerge.input.receiverThreadIds.length === 1, 'Spawned child should merge into one visible agent');
|
||||
assert(collabApi.getCachedState('child-thread-a')?.label === '关闭验证代理', 'Structured child state should be cached by thread id');
|
||||
|
||||
const planCardTool = {
|
||||
...spawnedTool,
|
||||
id: 'tool-plan-progress',
|
||||
input: {
|
||||
...spawnedTool.input,
|
||||
receiverThreadIds: ['child-thread-plan'],
|
||||
agentsStates: {
|
||||
'child-thread-plan': {
|
||||
title: '进度验证代理',
|
||||
taskDescription: '验证子代理计划简报。',
|
||||
status: 'running',
|
||||
planProgress: { completed: 1, total: 3 },
|
||||
planCurrentStep: '同步父卡片进度',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const planCardElement = collabApi.renderCollabAgentToolElement(planCardTool);
|
||||
const planRows = findNodesByClass(planCardElement, 'collab-agent-item-plan');
|
||||
const planDots = findNodesByClass(planCardElement, 'plan-progress-dot');
|
||||
const planCounts = findNodesByClass(planCardElement, 'plan-progress-count');
|
||||
const planCurrentSteps = findNodesByClass(planCardElement, 'collab-agent-item-plan-current');
|
||||
assert(planRows.length === 1, 'Sub-agent plan summary should render one compact progress row');
|
||||
assert(planDots.length === 3, 'Sub-agent plan summary should render one progress dot per short plan item');
|
||||
assert(planCounts.length === 1 && planCounts[0].textContent === '1/3', 'Sub-agent plan summary should always render completed/total');
|
||||
assert(planCurrentSteps.length === 1 && planCurrentSteps[0].textContent === '当前:同步父卡片进度', 'Sub-agent plan summary should render the current in-progress step');
|
||||
|
||||
collabApi.rememberCollabAgentState(
|
||||
'unrelated-child',
|
||||
{ title: '无关历史代理', status: 'running' },
|
||||
@@ -1974,6 +2007,20 @@ function assertCodexAppRuntimeSubAgentActivityContract() {
|
||||
fullText: '',
|
||||
};
|
||||
|
||||
assert(typeof runtime.planUpdateFromNotification === 'function', 'Runtime should expose shared plan notification parsing');
|
||||
const childPlan = runtime.planUpdateFromNotification({
|
||||
method: 'turn/plan/updated',
|
||||
params: {
|
||||
plan: [
|
||||
{ step: '解析子代理计划', status: 'completed' },
|
||||
{ step: '同步父卡片进度', status: 'in_progress' },
|
||||
{ step: '补充回归验证', status: 'pending' },
|
||||
],
|
||||
},
|
||||
});
|
||||
assert(childPlan?.progress?.completed === 1 && childPlan?.progress?.total === 3, 'Shared plan parser should summarize child plan progress');
|
||||
assert(childPlan?.currentStep === '同步父卡片进度', 'Shared plan parser should expose the current in-progress step');
|
||||
|
||||
runtime.processCodexAppNotification(entry, {
|
||||
method: 'item/started',
|
||||
params: {
|
||||
@@ -3423,6 +3470,47 @@ function assertCodexAppUnroutedNotificationRoutingContract() {
|
||||
);
|
||||
}
|
||||
|
||||
function assertMultiAgentV2CompatibilityContract() {
|
||||
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
|
||||
const runtimeSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'agent-runtime.js'), 'utf8');
|
||||
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
|
||||
assert(
|
||||
/CODEX_REASONING_LEVELS\s*=\s*new Set\(\[[^\]]*'ultra'/.test(serverSource),
|
||||
'Codex config model parsing should accept the ultra reasoning level'
|
||||
);
|
||||
assert(
|
||||
/low\|medium\|high\|xhigh\|ultra/.test(runtimeSource),
|
||||
'Codex CLI model suffix parsing should accept ultra'
|
||||
);
|
||||
assert(
|
||||
frontendSource.includes("{ value: 'ultra', label: 'ultra'"),
|
||||
'Codex model picker should expose ultra reasoning'
|
||||
);
|
||||
const instructionsBlock = serverSource.slice(
|
||||
serverSource.indexOf('const CODEX_APP_COLLABORATION_INSTRUCTIONS'),
|
||||
serverSource.indexOf('function getLocalCodexConfigTomlPath')
|
||||
);
|
||||
assert(instructionsBlock.includes('current runtime tool schema'), 'Sub-agent guidance should defer to the current runtime tool schema');
|
||||
assert(!instructionsBlock.includes('fork_context'), 'Sub-agent guidance should not hard-code the V1 fork_context field');
|
||||
assert(!instructionsBlock.includes('additional wait_agent rounds'), 'Sub-agent guidance should not impose obsolete repeated wait_agent calls');
|
||||
|
||||
const syncBlock = extractFunctionSource(serverSource, 'syncCcwebMcpChildAgentsFromCollabItem');
|
||||
assert(syncBlock.includes("itemType === 'subAgentActivity'"), 'Child routing should register canonical V2 subAgentActivity items');
|
||||
const notificationBlock = extractFunctionSource(serverSource, 'handleCodexAppNotification');
|
||||
const activitySyncIndex = notificationBlock.indexOf('syncCcwebMcpChildAgentsFromCollabItem(routed, item)');
|
||||
const childReturnIndex = notificationBlock.indexOf("if (routed.role === 'child')");
|
||||
assert(activitySyncIndex >= 0 && activitySyncIndex < childReturnIndex, 'Nested subAgentActivity registration should happen before child notification routing returns');
|
||||
const childNotificationBlock = extractFunctionSource(serverSource, 'processCcwebMcpChildNotification');
|
||||
assert(childNotificationBlock.includes('codexAppRuntime.planUpdateFromNotification(notification)'), 'Child notification routing should reuse runtime plan parsing');
|
||||
const recoveryBlock = extractFunctionSource(serverSource, 'recoverCcwebMcpChildThreadsFromPersistedToolCalls');
|
||||
assert(recoveryBlock.includes('parseMaybeJsonObject(tool?.result)'), 'Child recovery should read the latest persisted collaboration tool result');
|
||||
assert(recoveryBlock.includes('recoveredState.planProgress'), 'Child recovery should restore persisted plan progress');
|
||||
|
||||
assert(frontendSource.includes("closeBtn.textContent = '中断';"), 'Sub-agent action should use interrupt semantics');
|
||||
assert(frontendSource.includes('entry.agentPath ? `路径: ${entry.agentPath}`'), 'Sub-agent cards should expose the canonical agent path');
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -3440,6 +3528,7 @@ async function main() {
|
||||
if (regressionTarget === 'subagent-card-metadata') {
|
||||
assertFrontendSubagentCardMetadataContract();
|
||||
assertCodexAppRuntimeSubAgentActivityContract();
|
||||
assertMultiAgentV2CompatibilityContract();
|
||||
console.log('Subagent card metadata regression checks passed.');
|
||||
return;
|
||||
}
|
||||
@@ -3503,6 +3592,7 @@ async function main() {
|
||||
assertSessionSwitchResilienceContract();
|
||||
assertSessionSwitchRaceContract();
|
||||
assertCodexAppChildToolFallbackContract();
|
||||
assertMultiAgentV2CompatibilityContract();
|
||||
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-web-regression-'));
|
||||
const configDir = path.join(tempRoot, 'config');
|
||||
@@ -3580,7 +3670,7 @@ async function main() {
|
||||
}, null, 2));
|
||||
|
||||
createFakeClaudeHistory(homeDir);
|
||||
createFakeCodexConfig(homeDir);
|
||||
createFakeCodexConfig(homeDir, { reasoningEffort: 'ultra' });
|
||||
const codexFixture = createFakeCodexHistory(homeDir);
|
||||
const codexAppImportFixture = createFakeCodexHistory(homeDir, {
|
||||
threadId: 'codexapp-import-thread',
|
||||
@@ -3730,7 +3820,7 @@ async function main() {
|
||||
ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', cwd: codexInitCwd, mode: 'plan' }));
|
||||
const codexSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codex' && msg.cwd === codexInitCwd);
|
||||
assert(codexSession.mode === 'plan', 'Codex new_session should follow requested mode');
|
||||
assert(codexSession.model === 'gpt-5.5(xhigh)', 'Codex new_session should read default model from ~/.codex/config.toml');
|
||||
assert(codexSession.model === 'gpt-5.5(ultra)', 'Codex new_session should preserve ultra from ~/.codex/config.toml');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'set_session_pinned', sessionId: codexSession.sessionId, pinned: true }));
|
||||
const pinnedAck = await nextMessage(messages, ws, (msg) => msg.type === 'session_pinned' && msg.sessionId === codexSession.sessionId);
|
||||
@@ -4319,6 +4409,8 @@ async function main() {
|
||||
.split('\n')
|
||||
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(firstMessageSession.sessionId.slice(0, 8)));
|
||||
assert(spawnLine && !spawnLine.includes('--search') && spawnLine.includes('--image'), 'Codex exec should attach images and not append unsupported --search flag');
|
||||
const parsedSpawnLine = JSON.parse(spawnLine);
|
||||
assert(parsedSpawnLine.args.includes('model_reasoning_effort="ultra"'), 'Codex exec should pass the ultra reasoning level through model_reasoning_effort');
|
||||
|
||||
const allSpawnsForSession = processLog
|
||||
.trim()
|
||||
@@ -4383,7 +4475,7 @@ async function main() {
|
||||
].join('\n'));
|
||||
ws.send(JSON.stringify({ type: 'new_session', agent: 'codexapp', cwd: codexAppCwd, mode: 'yolo' }));
|
||||
const codexAppSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codexapp' && msg.cwd === codexAppCwd);
|
||||
assert(codexAppSession.model === 'gpt-5.5(xhigh)', 'Codex App new_session should read default Codex model');
|
||||
assert(codexAppSession.model === 'gpt-5.5(ultra)', 'Codex App new_session should preserve the ultra default Codex model');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-skill', trigger: '$', query: 'reg', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));
|
||||
const codexAppSkillComposer = await nextMessage(messages, ws, (msg) => msg.type === 'composer_suggestions' && msg.requestId === 'reg-codexapp-skill');
|
||||
@@ -4397,7 +4489,9 @@ async function main() {
|
||||
assert(/"mode":"default"/.test(codexAppDefaultCollab.text || ''), 'Codex App YOLO mode should pass default collaboration mode');
|
||||
assert(/"hasModel":true/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should include model');
|
||||
assert(/"hasDeveloperInstructions":true/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should include sub-agent developer instructions');
|
||||
assert(/"hasWaitAgentRetryGuidance":true/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should include wait_agent retry guidance');
|
||||
assert(/"hasSchemaDrivenSubagents":true/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should use runtime-schema-driven sub-agent guidance');
|
||||
assert(/"hasLegacyV1Guidance":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should omit legacy V1 fork/wait guidance');
|
||||
assert(/"reasoningEffort":"ultra"/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should pass ultra reasoning_effort');
|
||||
assert(/"hasTopLevelModel":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration turn should not duplicate model at top level');
|
||||
assert(/"hasTopLevelEffort":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration turn should not duplicate effort at top level');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
@@ -4775,13 +4869,13 @@ async function main() {
|
||||
assert(/finalMessage/.test(ccwebMcpChildReturned.tool?.result || ''), 'ccweb MCP child final message should be merged into the parent tool result');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
ws.send(JSON.stringify({ type: 'ccweb_mcp_child_agent_close', sessionId: codexAppSession.sessionId, threadId: 'child-thread-a' }));
|
||||
const ccwebMcpChildClosed = await nextMessage(messages, ws, (msg) =>
|
||||
const ccwebMcpChildInterrupted = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'ccweb_mcp_child_agent_update' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
msg.child?.threadId === 'child-thread-a' &&
|
||||
msg.child?.status === 'closed'
|
||||
msg.child?.status === 'interrupted'
|
||||
);
|
||||
assert(/"status": "closed"/.test(ccwebMcpChildClosed.tool?.result || ''), 'ccweb MCP child close should update the parent collab tool state');
|
||||
assert(/"status": "interrupted"/.test(ccwebMcpChildInterrupted.tool?.result || ''), 'ccweb MCP child interrupt should update the parent collab tool state');
|
||||
storedCodexApp = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
|
||||
const hasCollabTool = storedCodexApp.messages
|
||||
.flatMap((message) => Array.isArray(message.toolCalls) ? message.toolCalls : [])
|
||||
@@ -4791,13 +4885,76 @@ async function main() {
|
||||
.flatMap((message) => Array.isArray(message.toolCalls) ? message.toolCalls : [])
|
||||
.reverse()
|
||||
.find((tool) => tool.id === 'tool-collab');
|
||||
assert(/"status": "closed"/.test(persistedClosedCollabTool?.result || ''), 'ccweb MCP manual child close should persist closed state');
|
||||
assert(/"status": "interrupted"/.test(persistedClosedCollabTool?.result || ''), 'ccweb MCP manual child interrupt should persist interrupted state');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp subagent v2 prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
||||
const codexAppV2ChildStarted = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'ccweb_mcp_child_agent_update' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
msg.child?.threadId === 'child-thread-v2' &&
|
||||
msg.child?.status === 'running'
|
||||
);
|
||||
assert(codexAppV2ChildStarted.toolUseId === 'child-activity-v2', 'V2 child update should reference its subAgentActivity item');
|
||||
assert(codexAppV2ChildStarted.child.agentPath === '/root/v2_parent', 'V2 child update should preserve its canonical agentPath');
|
||||
const codexAppV2ChildPlan = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'ccweb_mcp_child_agent_update' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
msg.child?.threadId === 'child-thread-v2' &&
|
||||
msg.child?.planProgress?.completed === 1 &&
|
||||
msg.child?.planProgress?.total === 3
|
||||
);
|
||||
assert(codexAppV2ChildPlan.child.planCurrentStep === '同步父卡片进度', 'V2 child update should expose the current in-progress plan step');
|
||||
const codexAppV2ChildPlanToolResult = JSON.parse(codexAppV2ChildPlan.tool?.result || '{}');
|
||||
assert(
|
||||
codexAppV2ChildPlanToolResult.agentsStates?.['child-thread-v2']?.planProgress?.completed === 1,
|
||||
'V2 child plan progress should merge into the visible parent collaboration tool'
|
||||
);
|
||||
const codexAppV2GrandchildStarted = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'ccweb_mcp_child_agent_update' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
msg.child?.threadId === 'grandchild-thread-v2' &&
|
||||
msg.child?.status === 'running'
|
||||
);
|
||||
assert(codexAppV2GrandchildStarted.child.parentThreadId === 'child-thread-v2', 'Nested V2 child should retain the immediate child as parentThreadId');
|
||||
assert(codexAppV2GrandchildStarted.child.agentPath === '/root/v2_parent/v2_grandchild', 'Nested V2 child should preserve its canonical agentPath');
|
||||
const codexAppV2GrandchildReturned = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'ccweb_mcp_child_agent_update' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
msg.child?.threadId === 'grandchild-thread-v2' &&
|
||||
msg.child?.status === 'returned' &&
|
||||
/V2 孙代理最终消息/.test(msg.child?.candidateResult || '')
|
||||
);
|
||||
assert(/grandchild-thread-v2/.test(codexAppV2GrandchildReturned.tool?.result || ''), 'Nested V2 child result should merge into a visible collaboration tool');
|
||||
const codexAppV2ChildReturned = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'ccweb_mcp_child_agent_update' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
msg.child?.threadId === 'child-thread-v2' &&
|
||||
msg.child?.status === 'returned' &&
|
||||
/V2 子代理最终消息/.test(msg.child?.candidateResult || '')
|
||||
);
|
||||
assert(codexAppV2ChildReturned.child.parentThreadId, 'V2 child should retain the root parent thread id');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
storedCodexApp = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
|
||||
const persistedV2ChildTool = storedCodexApp.messages
|
||||
.flatMap((message) => Array.isArray(message.toolCalls) ? message.toolCalls : [])
|
||||
.find((tool) => tool.id === 'child-activity-v2');
|
||||
const persistedV2ChildResult = JSON.parse(persistedV2ChildTool?.result || '{}');
|
||||
assert(
|
||||
persistedV2ChildResult.agentsStates?.['child-thread-v2']?.planProgress?.completed === 1
|
||||
&& persistedV2ChildResult.agentsStates?.['child-thread-v2']?.planProgress?.total === 3,
|
||||
'V2 child plan progress should persist in the session collaboration tool for refresh recovery'
|
||||
);
|
||||
assert(
|
||||
persistedV2ChildResult.agentsStates?.['child-thread-v2']?.planCurrentStep === '同步父卡片进度',
|
||||
'V2 child current plan step should persist for refresh recovery'
|
||||
);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp collaboration plan probe', sessionId: codexAppSession.sessionId, mode: 'plan', agent: 'codexapp' }));
|
||||
const codexAppPlanCollab = await nextMessage(messages, ws, (msg) => msg.type === 'text_delta' && msg.sessionId === codexAppSession.sessionId && /collaboration mode:/.test(msg.text || ''));
|
||||
assert(/"mode":"plan"/.test(codexAppPlanCollab.text || ''), 'Codex App Plan mode should pass plan collaboration mode');
|
||||
assert(/"hasDeveloperInstructions":true/.test(codexAppPlanCollab.text || ''), 'Codex App Plan collaboration settings should keep sub-agent developer instructions');
|
||||
assert(/"hasWaitAgentRetryGuidance":true/.test(codexAppPlanCollab.text || ''), 'Codex App Plan collaboration settings should keep wait_agent retry guidance');
|
||||
assert(/"hasSchemaDrivenSubagents":true/.test(codexAppPlanCollab.text || ''), 'Codex App Plan collaboration settings should keep runtime-schema-driven guidance');
|
||||
assert(/"hasLegacyV1Guidance":false/.test(codexAppPlanCollab.text || ''), 'Codex App Plan collaboration settings should omit legacy V1 guidance');
|
||||
assert(/"hasTopLevelModel":false/.test(codexAppPlanCollab.text || ''), 'Codex App Plan collaboration turn should not duplicate model at top level');
|
||||
assert(/"hasTopLevelEffort":false/.test(codexAppPlanCollab.text || ''), 'Codex App Plan collaboration turn should not duplicate effort at top level');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
Reference in New Issue
Block a user