feat: support conversation state events in JavaScript sessions
This commit is contained in:
60
server.js
60
server.js
@@ -7540,6 +7540,62 @@ function scriptGetLastMessageApi(args = {}) {
|
||||
return Promise.resolve({ ok: true, conversationId, message });
|
||||
}
|
||||
|
||||
const JAVASCRIPT_CONVERSATION_IDLE_STABILITY_MS = 500;
|
||||
|
||||
function javascriptConversationStatusValue(conversationId) {
|
||||
reconcilePendingCrossConversationReplies();
|
||||
if (isSessionRunning(conversationId)) return 'running';
|
||||
if (crossConversationWaitState(conversationId).waitingOnChildren) return 'waiting_for_children';
|
||||
return 'idle';
|
||||
}
|
||||
|
||||
async function scriptGetConversationStatusApi(args = {}) {
|
||||
const conversationId = sanitizeId(args.conversationId || '');
|
||||
if (!conversationId) return scriptApiError('conversation_not_found', 'conversationId 无效。');
|
||||
if (!loadSession(conversationId)) {
|
||||
return scriptApiError('conversation_not_found', '目标对话不存在。', { conversationId });
|
||||
}
|
||||
|
||||
let status = javascriptConversationStatusValue(conversationId);
|
||||
if (status === 'idle') {
|
||||
// 等待态登记与运行态清理之间可能短暂出现 idle,稳定窗口后再确认。
|
||||
await new Promise((resolve) => setTimeout(resolve, JAVASCRIPT_CONVERSATION_IDLE_STABILITY_MS));
|
||||
if (!loadSession(conversationId)) {
|
||||
return scriptApiError('conversation_not_found', '目标对话不存在。', { conversationId });
|
||||
}
|
||||
status = javascriptConversationStatusValue(conversationId);
|
||||
}
|
||||
|
||||
return { ok: true, conversationId, status };
|
||||
}
|
||||
|
||||
function scriptGetChildConversationIdsApi(args = {}) {
|
||||
const conversationId = sanitizeId(args.conversationId || '');
|
||||
if (!conversationId) return scriptApiError('conversation_not_found', 'conversationId 无效。');
|
||||
if (!loadSession(conversationId)) {
|
||||
return scriptApiError('conversation_not_found', '目标对话不存在。', { conversationId });
|
||||
}
|
||||
|
||||
try {
|
||||
const children = fs.readdirSync(SESSIONS_DIR)
|
||||
.filter((name) => name.endsWith('.json'))
|
||||
.map((name) => loadSessionMetaFromFile(path.join(SESSIONS_DIR, name)))
|
||||
.filter((meta) => (
|
||||
meta
|
||||
&& meta.createdFromKind === 'mcp'
|
||||
&& meta.createdFromSourceSessionId === conversationId
|
||||
))
|
||||
.sort(compareSessionsForList);
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
conversationId,
|
||||
childConversationIds: children.map((meta) => meta.id),
|
||||
});
|
||||
} catch (error) {
|
||||
return scriptApiError('conversation_children_read_failed', `读取子对话失败:${error.message}`, { conversationId });
|
||||
}
|
||||
}
|
||||
|
||||
function validateSemanticCandidates(rawSemantics) {
|
||||
if (!Array.isArray(rawSemantics)) return { ok: false, error: scriptApiError('semantic_branch_invalid', '语义数组必须是数组。') };
|
||||
const values = rawSemantics.map((item) => (typeof item === 'string' ? item : null));
|
||||
@@ -7648,6 +7704,8 @@ function callInternalMcpTool(tool, args, sourceSessionId, sourceHopCount, script
|
||||
case 'ccweb_script_send_message':
|
||||
case 'ccweb_script_select_semantic_branch':
|
||||
case 'ccweb_script_get_last_message':
|
||||
case 'ccweb_script_get_conversation_status':
|
||||
case 'ccweb_script_get_child_conversation_ids':
|
||||
if (!scriptContext.authorized) return mcpToolError('script_authorization_required', '该能力只能由已授权的 JavaScript 脚本调用。');
|
||||
return javascriptSessionRuntime?.handleSessionCall(tool, args, sourceSessionId)
|
||||
|| mcpToolError('javascript_runtime_unavailable', 'JavaScript 脚本运行时尚未就绪。');
|
||||
@@ -7931,6 +7989,8 @@ function initializeJavascriptSessionRuntime() {
|
||||
sendMessage: scriptSendMessageApi,
|
||||
selectSemanticBranch: scriptSelectSemanticBranchApi,
|
||||
getLastMessage: scriptGetLastMessageApi,
|
||||
getConversationStatus: scriptGetConversationStatusApi,
|
||||
getChildConversationIds: scriptGetChildConversationIdsApi,
|
||||
},
|
||||
});
|
||||
javascriptSessionRuntime.recover();
|
||||
|
||||
Reference in New Issue
Block a user