feat(h5): web 联网能力、实时查询路由与 session Finish 对齐
- 新增 web 能力并挂载 platform/web(web_search/fetch_url) - 实时查询强制 web skill,router fallback 与 await session Finish - Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-19
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { isRunStreamReplayEnabled } from './agent-run-stream.mjs';
|
||||
import { isDirectChatSessionId } from './direct-chat-service.mjs';
|
||||
import { CHAT_INTENT_ROUTE, resolveLegacyRouteFromClassification } from './chat-intent-router.mjs';
|
||||
import { CHAT_INTENT_ROUTE, resolveGatewayAgentSessionId, resolveLegacyRouteFromClassification, logRouterDecisionShadow } from './chat-intent-router.mjs';
|
||||
import { resolveSessionAccess } from './session-broker.mjs';
|
||||
import {
|
||||
loadSnapshotMessages,
|
||||
@@ -21,6 +21,12 @@ const DEFAULT_RUN_TIMEOUT_MS = 15 * 60 * 1000;
|
||||
const DEFAULT_RUN_HEARTBEAT_MS = 30 * 1000;
|
||||
const TOOL_GATEWAY_SUMMARY_LIMIT = 4096;
|
||||
|
||||
function envFlag(value, fallback = false) {
|
||||
const raw = String(value ?? '').trim().toLowerCase();
|
||||
if (!raw) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||||
}
|
||||
|
||||
function nowMs() {
|
||||
return Date.now();
|
||||
}
|
||||
@@ -33,6 +39,12 @@ function safeJsonParse(value, fallback = null) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseDbJsonColumn(value, fallback = null) {
|
||||
if (value == null || value === '') return fallback;
|
||||
if (typeof value === 'object') return value;
|
||||
return safeJsonParse(value, fallback);
|
||||
}
|
||||
|
||||
function serializeMessage(message) {
|
||||
return JSON.stringify(message ?? {});
|
||||
}
|
||||
@@ -49,12 +61,6 @@ function timeoutError(ms) {
|
||||
return err;
|
||||
}
|
||||
|
||||
function envFlag(value, fallback = false) {
|
||||
const raw = String(value ?? '').trim().toLowerCase();
|
||||
if (!raw) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||||
}
|
||||
|
||||
export function normalizeAgentRunToolMode(value) {
|
||||
const normalized = String(value ?? 'chat').trim().toLowerCase();
|
||||
if (!normalized || normalized === 'chat') return 'chat';
|
||||
@@ -471,6 +477,10 @@ export function createAgentRunGateway({
|
||||
const routing = await resolveRunRouting(row, userMessage, runOptions);
|
||||
const routingDecision = resolveLegacyRouteFromClassification(routing) ?? routing?.route ?? null;
|
||||
if (routing) {
|
||||
logRouterDecisionShadow(routing, {
|
||||
requestId: row.request_id ?? null,
|
||||
sessionId: row.agent_session_id ?? null,
|
||||
});
|
||||
await appendEvent(runId, 'intent_routed', routing);
|
||||
if (routingDecision === CHAT_INTENT_ROUTE.AGENT && chatIntentRouter?.applyAgentOrchestration) {
|
||||
const grantedSkills = await resolveGrantedSkills(row.user_id);
|
||||
@@ -578,7 +588,11 @@ export function createAgentRunGateway({
|
||||
return { sessionId: row.agent_session_id ?? null };
|
||||
}
|
||||
|
||||
let sessionId = row.agent_session_id ?? null;
|
||||
let sessionId = resolveGatewayAgentSessionId({
|
||||
agentSessionId: row.agent_session_id ?? null,
|
||||
classification: routing,
|
||||
forceDeepReasoning: runOptions.forceDeepReasoning,
|
||||
});
|
||||
let escalatedDirectSessionId = null;
|
||||
if (isDirectChatSessionId(sessionId)) {
|
||||
escalatedDirectSessionId = sessionId;
|
||||
@@ -640,16 +654,37 @@ export function createAgentRunGateway({
|
||||
});
|
||||
}
|
||||
await invalidatePortalDirectChatSnapshot(sessionId);
|
||||
await tkmindProxy.submitSessionReplyForUser(
|
||||
row.user_id,
|
||||
sessionId,
|
||||
row.request_id,
|
||||
ensureGooseUserMessageMetadata(userMessage),
|
||||
{
|
||||
toolMode: runOptions.toolMode,
|
||||
forceDeepReasoning: runOptions.forceDeepReasoning,
|
||||
},
|
||||
);
|
||||
const awaitSessionFinish = envFlag(process.env.MEMIND_AGENT_RUN_AWAIT_SESSION_FINISH, true)
|
||||
&& runOptions.toolMode === 'chat'
|
||||
&& typeof tkmindProxy.submitSessionReplyAndAwaitFinishForUser === 'function';
|
||||
if (awaitSessionFinish) {
|
||||
const finish = await tkmindProxy.submitSessionReplyAndAwaitFinishForUser(
|
||||
row.user_id,
|
||||
sessionId,
|
||||
row.request_id,
|
||||
ensureGooseUserMessageMetadata(userMessage),
|
||||
{
|
||||
toolMode: runOptions.toolMode,
|
||||
forceDeepReasoning: runOptions.forceDeepReasoning,
|
||||
timeoutMs: runTimeoutMs,
|
||||
},
|
||||
);
|
||||
await appendEvent(runId, 'session_finished', {
|
||||
sessionId,
|
||||
tokenState: finish.tokenState ?? null,
|
||||
});
|
||||
} else {
|
||||
await tkmindProxy.submitSessionReplyForUser(
|
||||
row.user_id,
|
||||
sessionId,
|
||||
row.request_id,
|
||||
ensureGooseUserMessageMetadata(userMessage),
|
||||
{
|
||||
toolMode: runOptions.toolMode,
|
||||
forceDeepReasoning: runOptions.forceDeepReasoning,
|
||||
},
|
||||
);
|
||||
}
|
||||
return { sessionId };
|
||||
}
|
||||
|
||||
@@ -966,7 +1001,7 @@ export function createAgentRunGateway({
|
||||
events: rows.map((row) => ({
|
||||
id: row.id,
|
||||
eventType: row.event_type,
|
||||
data: row.data_json ? JSON.parse(row.data_json) : null,
|
||||
data: parseDbJsonColumn(row.data_json, null),
|
||||
createdAt: Number(row.created_at),
|
||||
})),
|
||||
cursorMiss,
|
||||
|
||||
Reference in New Issue
Block a user