feat: overhaul task board and cross-conversation workflows
This commit is contained in:
@@ -11,6 +11,24 @@ const SERVER_INFO = {
|
||||
version: '1.0.0',
|
||||
};
|
||||
|
||||
const CCWEB_REPLY_MODES = Object.freeze({
|
||||
ONE_WAY: 'one_way',
|
||||
RETURN_AND_CONTINUE: 'return_and_continue',
|
||||
});
|
||||
const CCWEB_SEND_MESSAGE_DESCRIPTION = '向指定 ccweb 对话发送一条消息,并以“来自某对话”的气泡在目标对话中展示。必须填写 replyMode:仅当来源不需要目标结果时使用 one_way;涉及分析、实现、测试、验收、完成后汇报或来源后续依赖目标结果时,必须使用 return_and_continue。工具调用会立即返回,不阻塞也不等待目标对话完成。';
|
||||
const CODEX_APP_COMMUNICATION_TOOL_NAMES = new Set([
|
||||
'ccweb_list_conversations',
|
||||
'ccweb_set_title',
|
||||
'ccweb_create_conversation',
|
||||
'ccweb_send_message',
|
||||
'ccweb_list_pending_replies',
|
||||
'ccweb_get_pending_reply',
|
||||
]);
|
||||
const HIDDEN_CALLABLE_TOOL_NAMES = new Set([
|
||||
'ccweb_request_reply',
|
||||
'ccweb_task_update',
|
||||
]);
|
||||
|
||||
const TOOLS = [
|
||||
{
|
||||
name: 'ccweb_display_image',
|
||||
@@ -28,10 +46,15 @@ const TOOLS = [
|
||||
},
|
||||
{
|
||||
name: 'ccweb_list_conversations',
|
||||
description: '列出当前 ccweb 中可投递消息的对话。只返回 ID、标题、Agent、运行状态和更新时间,不返回对话正文。',
|
||||
description: '列出当前 ccweb 中可投递消息的对话。默认返回全部对话;scope=children 时只返回当前来源对话通过 ccweb_create_conversation 直接创建的持久子对话。只返回 ID、标题、Agent、运行状态和更新时间,不返回对话正文。',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
scope: {
|
||||
type: 'string',
|
||||
enum: ['all', 'children'],
|
||||
description: '可选。all 返回全部对话;children 只返回当前来源对话直接创建的持久子对话,默认 all。',
|
||||
},
|
||||
agent: {
|
||||
type: 'string',
|
||||
enum: ['claude', 'codex', 'codexapp'],
|
||||
@@ -102,7 +125,7 @@ const TOOLS = [
|
||||
},
|
||||
{
|
||||
name: 'ccweb_send_message',
|
||||
description: '向指定 ccweb 对话发送一条消息,并以“来自某对话”的气泡在目标对话中展示。',
|
||||
description: CCWEB_SEND_MESSAGE_DESCRIPTION,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -114,8 +137,13 @@ const TOOLS = [
|
||||
type: 'string',
|
||||
description: '要发送到目标对话的纯文本消息。',
|
||||
},
|
||||
replyMode: {
|
||||
type: 'string',
|
||||
enum: Object.values(CCWEB_REPLY_MODES),
|
||||
description: '回传模式。one_way 表示单向投递且目标输出只留在目标对话;return_and_continue 表示目标完成后自动回传结果并触发来源继续运行。',
|
||||
},
|
||||
},
|
||||
required: ['targetConversationId', 'content'],
|
||||
required: ['targetConversationId', 'content', 'replyMode'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
@@ -149,25 +177,6 @@ const TOOLS = [
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ccweb_request_reply',
|
||||
description: '向指定 ccweb 对话发送一条消息,并在目标对话本轮输出完成后把回复写回当前对话,然后继续触发当前对话运行。',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
targetConversationId: {
|
||||
type: 'string',
|
||||
description: '目标对话 ID。',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: '要发送到目标对话的纯文本消息。',
|
||||
},
|
||||
},
|
||||
required: ['targetConversationId', 'content'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ccweb_prompt_user',
|
||||
description: '在当前来源 ccweb 对话前台渲染一个多问题表单。工具会立即返回,不等待用户;用户提交后,ccweb 会把问题、选择和答案作为一条普通用户消息发回当前对话。',
|
||||
@@ -278,6 +287,17 @@ function imageMimeFromPath(filePath) {
|
||||
return ({ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif' })[ext] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
function isCallableToolName(name) {
|
||||
const toolName = String(name || '');
|
||||
return TOOLS.some((tool) => tool.name === toolName) || HIDDEN_CALLABLE_TOOL_NAMES.has(toolName);
|
||||
}
|
||||
|
||||
function codexAppCommunicationTools() {
|
||||
return TOOLS
|
||||
.filter((tool) => CODEX_APP_COMMUNICATION_TOOL_NAMES.has(tool.name))
|
||||
.map((tool) => ({ ...tool, namespace: 'ccweb' }));
|
||||
}
|
||||
|
||||
function prepareImagePayload(args = {}) {
|
||||
const source = String(args.source || '').trim();
|
||||
if (!source) return { ok: false, code: 'missing_source', message: 'source 不能为空。' };
|
||||
@@ -415,6 +435,12 @@ function toolResponse(payload) {
|
||||
};
|
||||
}
|
||||
|
||||
async function listToolsForCurrentSource() {
|
||||
const payload = await callCcweb('ccweb_internal_tools_list', {});
|
||||
if (!payload?.ok || !Array.isArray(payload.tools)) return TOOLS;
|
||||
return [...TOOLS, ...payload.tools];
|
||||
}
|
||||
|
||||
async function handleRequest(message) {
|
||||
const { id, method } = message;
|
||||
const hasId = Object.prototype.hasOwnProperty.call(message, 'id');
|
||||
@@ -435,12 +461,12 @@ async function handleRequest(message) {
|
||||
jsonRpcResult(id, {});
|
||||
break;
|
||||
case 'tools/list':
|
||||
jsonRpcResult(id, { tools: TOOLS });
|
||||
jsonRpcResult(id, { tools: await listToolsForCurrentSource() });
|
||||
break;
|
||||
case 'tools/call': {
|
||||
const name = String(message.params?.name || '');
|
||||
const args = message.params?.arguments || {};
|
||||
if (!TOOLS.some((tool) => tool.name === name)) {
|
||||
if (!isCallableToolName(name)) {
|
||||
jsonRpcResult(id, toolResponse({
|
||||
ok: false,
|
||||
code: 'unknown_tool',
|
||||
@@ -493,7 +519,14 @@ function runStdioServer() {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { TOOLS, prepareImagePayload, runStdioServer };
|
||||
module.exports = {
|
||||
TOOLS,
|
||||
CCWEB_REPLY_MODES,
|
||||
codexAppCommunicationTools,
|
||||
isCallableToolName,
|
||||
prepareImagePayload,
|
||||
runStdioServer,
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
runStdioServer();
|
||||
|
||||
Reference in New Issue
Block a user