feat: 优化任务状态与图片 MCP 提示

This commit is contained in:
shiyue
2026-08-13 00:34:37 +08:00
parent 86231f0d97
commit 214ff4ec88
13 changed files with 1353 additions and 49 deletions

View File

@@ -662,6 +662,7 @@ function assertTaskBoardIntegrationContract() {
const taskBoardServiceSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'task-board-service.js'), 'utf8');
const taskBoardMcpSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'task-board-mcp.js'), 'utf8');
const taskBoardLifecycleSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'task-board-lifecycle.js'), 'utf8');
const taskBoardClassifierSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'task-board-classifier.js'), 'utf8');
const taskBoardFrontendSource = fs.readFileSync(path.join(PUBLIC_DIR, 'task-board.js'), 'utf8');
const taskBoardStyleSource = fs.readFileSync(path.join(PUBLIC_DIR, 'task-board.css'), 'utf8');
const trackingIdCount = (indexSource.match(/id="task-tracking-control"/g) || []).length;
@@ -682,6 +683,11 @@ function assertTaskBoardIntegrationContract() {
const internalMcpSource = extractFunctionSource(serverSource, 'callInternalMcpTool');
const composerSource = extractFunctionSource(serverSource, 'listComposerSuggestions');
const composerMcpSource = extractFunctionSource(serverSource, 'listComposerMcpItems');
const startCodexAppTurnSource = extractFunctionSource(serverSource, 'startCodexAppTurn');
const handleMessageSource = extractFunctionSource(serverSource, 'handleMessage');
const handleTurnCompleteSource = extractFunctionSource(serverSource, 'handleCodexAppTurnComplete');
const handleSteerSource = extractFunctionSource(serverSource, 'handleCodexAppSteerMessage');
const saveSessionSource = extractFunctionSource(serverSource, 'saveSession');
assert(!serverSource.includes('function taskBoardFacets(') && !queryPayloadSource.includes('facets'), 'Task board queries should not expose retired Agent facets');
assert(!filtersSource.includes("'agent'") && !filtersSource.includes('source.agent') && !filtersSource.includes('filters.agent'), 'Task board queries should ignore retired Agent filters');
assert(!filtersSource.includes('source.priority') && !filtersSource.includes('filters.priority'), 'Task board queries should not forward the retired priority filter');
@@ -738,10 +744,32 @@ function assertTaskBoardIntegrationContract() {
'Task status WebSocket and MCP responses should not retain ignored-write branches'
);
assert(
serverSource.includes('ensureTaskBoardMcpToolsFresh(client, session, currentThreadId)')
&& serverSource.includes('taskSchema: taskSchemaFingerprint')
&& serverSource.includes("client.reloadMcpServers()"),
'Codex App turns should invalidate cached MCP tools and carry a schema fingerprint fallback'
!serverSource.includes('ensureTaskBoardMcpToolsFresh')
&& !serverSource.includes('taskBoardMcpFingerprintBySession')
&& !startCodexAppTurnSource.includes('reloadMcpServers')
&& !startCodexAppTurnSource.includes('config/mcpServer/reload'),
'Automatic task classification must not reload MCP before Codex App turns'
);
assert(
taskBoardClassifierSource.includes("tools: []")
&& taskBoardClassifierSource.includes("tool_choice: 'none'")
&& taskBoardClassifierSource.includes("type: 'json_schema'")
&& taskBoardClassifierSource.includes('唯一职责是分类')
&& taskBoardClassifierSource.includes('不要执行、继续、检查或验证任务'),
'Task classifier should use a focused no-tool strict JSON request'
);
assert(
handleMessageSource.includes('TASK_STATUS_CLASSIFICATION_EVENTS.USER_MESSAGE_RECEIVED')
&& handleTurnCompleteSource.includes('TASK_STATUS_CLASSIFICATION_EVENTS.TURN_COMPLETED')
&& handleTurnCompleteSource.includes('!completionError')
&& handleTurnCompleteSource.includes('!options.interrupted')
&& handleTurnCompleteSource.includes('!entry.userAborted')
&& handleSteerSource.includes('TASK_STATUS_CLASSIFICATION_EVENTS.USER_MESSAGE_RECEIVED'),
'Codex App classification should run only at accepted user messages and successful turn completion'
);
assert(
saveSessionSource.includes('preserveNewerTaskTrackingForSessionSave(session, targetPath)'),
'Ordinary session persistence must preserve a newer task tracking version written by the classifier'
);
}
@@ -5597,6 +5625,8 @@ function assertCcwebDisplayImageContract() {
const { TOOLS, prepareImagePayload } = require(path.join(REPO_DIR, 'lib', 'ccweb-mcp-server'));
const imageHandler = server.slice(server.indexOf('function createCcwebDisplayImage'), server.indexOf('function findCcwebPromptMessage'));
assert(TOOLS.some((tool) => tool.name === 'ccweb_display_image'), 'ccweb MCP should expose the image display tool');
const displayImageTool = TOOLS.find((tool) => tool.name === 'ccweb_display_image');
assert(displayImageTool.description === '当你需要输出、显示图片时,请调用该 MCP。', 'Image tool description should state only its responsibility');
assert(server.includes("case 'ccweb_display_image':"), 'Internal MCP routing should handle image display calls');
assert(!imageHandler.includes("type: 'session_message'"), 'Image tool should not create a separate assistant message');
assert(frontend.includes('isCcwebDisplayImageTool'), 'Image tool results should render inside the current assistant bubble');
@@ -7133,8 +7163,8 @@ async function main() {
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
const reloadAfterTracking = await postAuthedJson(port, token, `/api/sessions/${codexAppSession.sessionId}/reload-mcp`);
assert(
Number(reloadAfterTracking.result?.reloadCount || 0) >= baselineMcpReloadCount + 2,
'Changing tracking should trigger one implicit Codex App MCP reload before the next turn'
Number(reloadAfterTracking.result?.reloadCount || 0) === baselineMcpReloadCount + 1,
'Task tracking changes must not trigger an implicit MCP reload; only the explicit reload request should count'
);
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-prompt-user-mcp', trigger: '/', query: 'prompt_user', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));