feat: support MCP elicitation and rebuild release
This commit is contained in:
@@ -2627,6 +2627,10 @@ function assertFrontendPrimaryCodexAppUiContract() {
|
||||
serverSource.includes("const VALID_AGENTS = new Set(['claude', 'codex', 'codexapp']);"),
|
||||
'Server explicit agent support should remain available for API/MCP paths'
|
||||
);
|
||||
assert(serverSource.includes("case 'mcpServer/elicitation/request':"), 'Server should route MCP elicitation requests through the interactive response path');
|
||||
assert(serverSource.includes('pendingCodexAppElicitations') && serverSource.includes('10 * 60 * 1000') && serverSource.includes('resolvePendingCodexAppElicitationsForSession'), 'MCP elicitation should keep a bounded pending lifecycle with timeout and session cleanup');
|
||||
assert(source.includes("case 'codex_app_elicitation_request':"), 'Frontend should render MCP elicitation requests');
|
||||
assert(source.includes("type: 'codex_app_elicitation_response'"), 'Frontend should send structured MCP elicitation responses');
|
||||
}
|
||||
|
||||
function assertSetTitleMcpContract() {
|
||||
@@ -2668,6 +2672,7 @@ function assertSessionItemTooltipContract() {
|
||||
function assertSessionProjectSnapshotContract() {
|
||||
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const mergeSource = extractFunctionSource(frontendSource, 'mergeSessionListSnapshot');
|
||||
const normalizeGiteaSourceSource = extractFunctionSource(frontendSource, 'normalizeGiteaSource');
|
||||
const api = new Function(`
|
||||
let sessions = [];
|
||||
function normalizeAgent(agent) {
|
||||
@@ -2680,6 +2685,7 @@ function assertSessionProjectSnapshotContract() {
|
||||
function compareSessionUpdatedDesc(a, b) {
|
||||
return new Date(b.updated || 0) - new Date(a.updated || 0);
|
||||
}
|
||||
${normalizeGiteaSourceSource}
|
||||
${mergeSource}
|
||||
return {
|
||||
mergeSessionListSnapshot,
|
||||
@@ -7991,6 +7997,43 @@ async function main() {
|
||||
assert(/guided answer: A/.test(guidedDelta.text || ''), 'Codex App should continue after guided input response');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp elicitation prompt', sessionId: codexAppSession.sessionId, mode: 'default', agent: 'codexapp' }));
|
||||
const elicitationRequest = await nextMessage(messages, ws, (msg) => msg.type === 'codex_app_elicitation_request' && msg.sessionId === codexAppSession.sessionId);
|
||||
assert(elicitationRequest.mode === 'form', 'Codex App should forward MCP form elicitation requests');
|
||||
assert(elicitationRequest.serverName === 'fixture-mcp', 'MCP elicitation should preserve server name');
|
||||
assert(elicitationRequest.requestedSchema?.properties?.displayName, 'MCP elicitation should forward requested schema');
|
||||
assert(!messages.some((msg) => msg.type === 'system_message' && /mcpServer\/elicitation\/request/.test(msg.message || '') && /保守策略拒绝/.test(msg.message || '')), 'MCP elicitation requests should not be rejected by the generic unsupported-request path');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'codex_app_elicitation_response',
|
||||
action: 'accept',
|
||||
sessionId: codexAppSession.sessionId,
|
||||
requestId: elicitationRequest.requestId,
|
||||
content: { displayName: '回归用户', count: 3, enabled: true, tags: ['alpha'] },
|
||||
}));
|
||||
const elicitationSubmitted = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && msg.sessionId === codexAppSession.sessionId && /已提交 MCP elicitation/.test(msg.message || ''));
|
||||
assert(/已提交 MCP elicitation/.test(elicitationSubmitted.message || ''), 'MCP elicitation acceptance should show confirmation hint');
|
||||
const elicitationDelta = await nextMessage(messages, ws, (msg) => msg.type === 'text_delta' && msg.sessionId === codexAppSession.sessionId && /回归用户/.test(msg.text || ''));
|
||||
assert(/回归用户/.test(elicitationDelta.text || ''), 'Codex App should continue after MCP elicitation response');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp elicitation openai', sessionId: codexAppSession.sessionId, mode: 'default', agent: 'codexapp' }));
|
||||
const openAiElicitationRequest = await nextMessage(messages, ws, (msg) => msg.type === 'codex_app_elicitation_request' && msg.sessionId === codexAppSession.sessionId);
|
||||
assert(openAiElicitationRequest.mode === 'openai/form', 'Codex App should forward openai/form elicitation requests');
|
||||
ws.send(JSON.stringify({ type: 'codex_app_elicitation_response', action: 'decline', sessionId: codexAppSession.sessionId, requestId: openAiElicitationRequest.requestId }));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && msg.sessionId === codexAppSession.sessionId && /已拒绝 MCP elicitation/.test(msg.message || ''));
|
||||
const openAiElicitationDelta = await nextMessage(messages, ws, (msg) => msg.type === 'text_delta' && msg.sessionId === codexAppSession.sessionId && /"action":"decline"/.test(msg.text || ''));
|
||||
assert(/"action":"decline"/.test(openAiElicitationDelta.text || ''), 'MCP openai/form elicitation should support decline');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp elicitation url', sessionId: codexAppSession.sessionId, mode: 'default', agent: 'codexapp' }));
|
||||
const urlElicitationRequest = await nextMessage(messages, ws, (msg) => msg.type === 'codex_app_elicitation_request' && msg.sessionId === codexAppSession.sessionId);
|
||||
assert(urlElicitationRequest.mode === 'url' && /example\.com/.test(urlElicitationRequest.url || ''), 'Codex App should forward URL elicitation requests');
|
||||
ws.send(JSON.stringify({ type: 'codex_app_elicitation_response', action: 'cancel', sessionId: codexAppSession.sessionId, requestId: urlElicitationRequest.requestId }));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && msg.sessionId === codexAppSession.sessionId && /已取消 MCP elicitation/.test(msg.message || ''));
|
||||
const urlElicitationDelta = await nextMessage(messages, ws, (msg) => msg.type === 'text_delta' && msg.sessionId === codexAppSession.sessionId && /"action":"cancel"/.test(msg.text || ''));
|
||||
assert(/"action":"cancel"/.test(urlElicitationDelta.text || ''), 'MCP URL elicitation should support cancel');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp approval prompt', sessionId: codexAppSession.sessionId, mode: 'default', agent: 'codexapp' }));
|
||||
const approvalRequest = await nextMessage(messages, ws, (msg) => msg.type === 'codex_app_approval_request' && msg.sessionId === codexAppSession.sessionId);
|
||||
assert(approvalRequest.method === 'item/commandExecution/requestApproval', 'Codex App should forward command approval requests');
|
||||
|
||||
Reference in New Issue
Block a user