'use strict'; const { TaskBoardError } = require('./task-board-service'); // 任务工具不能静态注册;缺少来源会话上下文时必须保持为空。 const TASK_BOARD_MCP_TOOL_DEFINITIONS = Object.freeze([]); const TASK_BOARD_MCP_TOOLS = TASK_BOARD_MCP_TOOL_DEFINITIONS; const TOOLS = TASK_BOARD_MCP_TOOL_DEFINITIONS; function isPlainObject(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) return false; const prototype = Object.getPrototypeOf(value); return prototype === Object.prototype || prototype === null; } function assertArgsObject(args, allowedKeys, toolName) { const value = args === undefined || args === null ? {} : args; if (!isPlainObject(value)) { throw new TaskBoardError('task_status_invalid', `${toolName} 参数必须是对象。`); } const unexpected = Object.keys(value).filter((key) => !allowedKeys.has(key)); if (unexpected.length > 0) { throw new TaskBoardError('task_status_invalid', `${toolName} 包含不支持的字段。`, { fields: unexpected, }); } return value; } function contextSessionId(context, resolver) { if (!isPlainObject(context)) { throw new TaskBoardError('task_session_not_found', 'MCP 调用上下文缺少来源会话。'); } let value; if (typeof resolver === 'function') value = resolver(context); if (value === undefined || value === null || value === '') { value = context.sourceSessionId ?? context.sessionId ?? context.source?.sessionId ?? context.mcp?.sourceSessionId; } // 来源只取 MCP 连接上下文,绝不接受模型参数中的 sessionId。 if (typeof value !== 'string' || value.length === 0) { throw new TaskBoardError('task_session_not_found', 'MCP 调用上下文缺少来源会话。'); } return value; } function actorFromContext(context) { const actorId = isPlainObject(context) ? (context.actorId || context.agentId || context.sourceAgentId || 'mcp') : 'mcp'; return { source: 'mcp', id: actorId }; } function errorPayload(error) { if (error instanceof TaskBoardError) { const payload = { ok: false, code: error.code, message: error.message }; if (error.details !== undefined) payload.details = error.details; return payload; } return { ok: false, code: 'task_status_invalid', message: '任务状态工具执行失败。', }; } function serviceFromOptions(serviceOrOptions) { if (serviceOrOptions && typeof serviceOrOptions.getTask === 'function' && typeof serviceOrOptions.updateStatus === 'function') { return serviceOrOptions; } if (isPlainObject(serviceOrOptions)) { if (serviceOrOptions.service) return serviceOrOptions.service; if (serviceOrOptions.taskBoardService) return serviceOrOptions.taskBoardService; } throw new TypeError('任务看板 MCP 需要 TaskBoardService。'); } function factoryOptionsFrom(serviceOrOptions, options) { return isPlainObject(serviceOrOptions) && !serviceOrOptions.getTask ? { ...serviceOrOptions, ...(isPlainObject(options) ? options : {}) } : (isPlainObject(options) ? options : {}); } function enabledDefinitions(service) { const snapshot = typeof service.getStatusDefinitionSnapshot === 'function' ? service.getStatusDefinitionSnapshot() : { definitions: service.getStatusDefinitions() }; return (Array.isArray(snapshot.definitions) ? snapshot.definitions : []) .filter((definition) => definition?.enabled === true); } function compactPrompt(value) { return String(value || '').replace(/\s+/g, ' ').trim(); } function taskUpdateDescription(task, definitions) { const current = task.status || definitions.find((definition) => ( definition.id === task.taskTracking.statusId )); const currentLabel = compactPrompt(current?.label) || task.taskTracking.statusId; const currentId = task.taskTracking.statusId; const mappings = definitions.map((definition) => ( `- ${compactPrompt(definition.label)} / ${definition.id}: ${compactPrompt(definition.prompt)}` )); return [ `更新当前来源会话的任务看板列。当前状态:${currentLabel} / ${currentId}。`, '仅当当前对话任务状态真实发生变化时调用;单轮开始、结束、运行停止不代表任务状态变化。', '可选列分类映射:', ...mappings, ].join('\n'); } function createTaskBoardMcpToolDefinitions(serviceOrOptions, context = {}, options = {}) { const service = serviceFromOptions(serviceOrOptions); const factoryOptions = factoryOptionsFrom(serviceOrOptions, options); const resolver = factoryOptions.sourceSessionIdResolver || factoryOptions.getSourceSessionId || null; try { const sessionId = contextSessionId(context, resolver); const task = service.getTask(sessionId); if (task.taskTracking.enabled !== true) return []; const definitions = enabledDefinitions(service); return [{ name: 'ccweb_task_update', description: taskUpdateDescription(task, definitions), inputSchema: { type: 'object', properties: { statusId: { type: 'string', enum: definitions.map((definition) => definition.id), description: '选择一个当前已启用的看板列 ID。', }, reason: { type: 'string', maxLength: 2000, description: '可选,简要说明状态变化原因。', }, summary: { type: 'string', maxLength: 4000, description: '可选,更新面向看板的任务摘要。', }, }, required: ['statusId'], additionalProperties: false, }, }]; } catch (error) { if (error instanceof TaskBoardError && (error.code === 'task_session_not_found' || error.code === 'task_tracking_disabled')) { return []; } throw error; } } function statusPayload(task) { return { ok: true, changed: task.changed === true, sessionId: task.sessionId, currentStatus: { ...task.taskTracking }, currentStatusDefinition: task.status ? { ...task.status } : null, task, }; } function createTaskBoardMcpHandlers(serviceOrOptions, options = {}) { const service = serviceFromOptions(serviceOrOptions); const factoryOptions = factoryOptionsFrom(serviceOrOptions, options); const sourceSessionResolver = factoryOptions.sourceSessionIdResolver || factoryOptions.getSourceSessionId || null; function updateStatus(args = {}, context = {}) { try { const input = assertArgsObject(args, new Set(['statusId', 'reason', 'summary']), 'ccweb_task_update'); const sessionId = contextSessionId(context, sourceSessionResolver); const task = service.updateStatus(sessionId, input, actorFromContext(context)); return statusPayload(task); } catch (error) { return errorPayload(error); } } return Object.freeze({ ccweb_task_update: updateStatus }); } function createTaskBoardMcpHandler(serviceOrOptions, options = {}) { const handlers = createTaskBoardMcpHandlers(serviceOrOptions, options); return function handleTaskBoardMcpCall(name, args = {}, context = {}) { const handler = handlers[name]; if (!handler) { return { ok: false, code: 'unknown_tool', message: `未知任务看板工具: ${String(name || '')}`, }; } return handler(args, context); }; } const createTaskBoardMcpAdapter = createTaskBoardMcpHandler; module.exports = { TASK_BOARD_MCP_TOOL_DEFINITIONS, TASK_BOARD_MCP_TOOLS, TOOLS, createTaskBoardMcpToolDefinitions, createTaskBoardMcpHandlers, createTaskBoardMcpHandler, createTaskBoardMcpAdapter, errorPayload, };