feat: support conversation state events in JavaScript sessions

This commit is contained in:
shiyue
2026-08-27 22:16:33 +08:00
parent e62a545569
commit 97b4a32dda
12 changed files with 458 additions and 11 deletions

View File

@@ -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();