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:
john
2026-07-06 16:06:26 +08:00
parent 08feae8bef
commit 14a00774d9
41 changed files with 3501 additions and 126 deletions
+54 -19
View File
@@ -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,