fix(agent): restore active-task routing and active-mode validation observation
Memind CI / Test, build, and release guards (push) Successful in 5m23s

Keep correction follow-ups on Agent using session transcript and active task
context, and let Page Data validation reach Orchestrator when mode is active
instead of requiring shadowEngine.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-27 12:15:15 +08:00
parent f95d766f69
commit aecde46ff6
6 changed files with 662 additions and 20 deletions
+234 -15
View File
@@ -16,6 +16,10 @@ import {
} from './memory-intervention.mjs';
import { filterMemoriesByQuery } from './memory-legacy-fallback.mjs';
import { matchDirectChatFaqRule, isExplicitTextOnlyRequest } from './chat-intent-router-rules.mjs';
import {
deriveAssistantFacingText,
deriveUserFacingText,
} from './conversation-display.mjs';
import { isGoalRunIntent } from './goal-run-intent.mjs';
import { pgvectorMemoryBackendInternals } from './memory-v2-pgvector.mjs';
@@ -84,6 +88,17 @@ const AGENT_SESSION_CONTINUE_PATTERNS = [
/^(?:开始吧|按默认做|默认方案|继续|就这样|就这样吧)[!!。.\s]*$/iu,
];
/** Corrections and refinements during an active Agent task must stay on Agent. */
const AGENT_TASK_FOLLOWUP_PATTERNS = [
/(?:还是|仍然|依然|并没|没有|并未).{0,16}(?:按照|按|理解|改|做|对|听)/u,
/(?:不对|错了|有误|有问题|不行|不好|不太对)/u,
/(?:更正|修正|修改|改一下|调整|优化|重新|再来|重做|再试)/u,
/(?:不听|没听|不按|没按|不照).{0,16}(?:指令|要求|说的|逻辑|意思)/u,
/(?:继续|接着).{0,16}(?:做|改|完善|优化|调整|处理|执行)/u,
/(?:按我(?:的|说)|照我(?:的|说)|我说的|我讲的|我上面)/u,
/(?:没(?:有)?按照|没有按).{0,20}(?:逻辑|要求|方案|口径|框架)/u,
];
/** Pure text chat/creative prompts — fast-path even when LLM router is enabled. */
const OBVIOUS_DIRECT_CHAT_PATTERNS = [
/(?:讲|说|来|编).{0,10}(?:个|一段|一首|一个)?(?:睡前故事|故事|笑话|段子)/u,
@@ -310,6 +325,84 @@ export function isAgentSessionContinueText(text) {
return AGENT_SESSION_CONTINUE_PATTERNS.some((pattern) => pattern.test(normalized));
}
export function isAgentTaskFollowUpText(text) {
const normalized = String(text ?? '').trim();
if (!normalized) return false;
return AGENT_TASK_FOLLOWUP_PATTERNS.some((pattern) => pattern.test(normalized));
}
function isExplicitDirectChatOnlyText(text) {
const normalized = String(text ?? '').trim();
if (!normalized) return false;
if (isMemoryRecallQuestion(normalized)) return true;
if (isExplicitTextOnlyRequest(normalized)) return true;
if (OBVIOUS_DIRECT_PATTERNS.some((pattern) => pattern.test(normalized))) return true;
if (OBVIOUS_DIRECT_CHAT_PATTERNS.some((pattern) => pattern.test(normalized))) return true;
const faqMatch = matchDirectChatFaqRule(normalized);
return Boolean(faqMatch);
}
export function shouldForceActiveAgentTaskContinuation({
activeTaskContext = null,
text = '',
sessionId = null,
sessionMessageCount = null,
} = {}) {
if (!activeTaskContext?.hasRecentAgentTask) return false;
if (!hasPriorAgentConversation(sessionId, sessionMessageCount)) return false;
const normalized = String(text ?? '').trim();
if (!normalized || isExplicitDirectChatOnlyText(normalized)) return false;
if (activeTaskContext.lastRunFailed === true) return true;
return isAgentTaskFollowUpText(normalized);
}
export function shouldDeferActiveTaskRoutingToLlm({
activeTaskContext = null,
text = '',
sessionId = null,
sessionMessageCount = null,
} = {}) {
if (!activeTaskContext?.hasRecentAgentTask) return false;
if (!hasPriorAgentConversation(sessionId, sessionMessageCount)) return false;
const normalized = String(text ?? '').trim();
if (!normalized || isExplicitDirectChatOnlyText(normalized)) return false;
if (shouldForceActiveAgentTaskContinuation({
activeTaskContext,
text: normalized,
sessionId,
sessionMessageCount,
})) {
return false;
}
return activeTaskContext.lastRoute !== 'direct_chat'
&& activeTaskContext.lastRoute !== 'chat';
}
/** @deprecated use shouldForceActiveAgentTaskContinuation */
export function shouldContinueActiveAgentTask(input = {}) {
return shouldForceActiveAgentTaskContinuation(input)
|| shouldDeferActiveTaskRoutingToLlm(input);
}
export function buildActiveTaskFollowUpClassification(activeTaskContext, { grantedSkills = [] } = {}) {
let suggestedSkill = String(activeTaskContext?.lastSuggestedSkill ?? '').trim() || null;
if (suggestedSkill && grantedSkills.length > 0 && !grantedSkills.includes(suggestedSkill)) {
suggestedSkill = null;
}
const agentBrief = suggestedSkill
? `继续完成未完成的 ${suggestedSkill} 任务;用户正在纠正或补充要求,必须调用工具执行,不要只回复文字。`
: '用户正在纠正或补充上一轮 Agent 任务,必须继续执行并产出结果,不要只回复文字。';
return normalizeClassification({
route: CHAT_INTENT_ROUTE.AGENT,
confidence: 0.92,
reason: activeTaskContext?.lastRunFailed
? '上一轮任务未完成,延续 Agent 执行'
: '活跃任务会话中的纠正或补充',
suggested_skill: suggestedSkill,
agent_brief: agentBrief,
}, { source: 'rule' });
}
export function isRealtimeInfoQuestion(text) {
const normalized = String(text ?? '').trim();
if (!normalized) return false;
@@ -541,6 +634,8 @@ function buildRouterSystemPrompt(grantedSkills = []) {
'- 用户要产出可访问页面、文件、链接,或需要工具/skills → agent_orchestration',
'- 用户询问实时赛况、新闻、天气、行情等需要联网查询的问题 → agent_orchestrationsuggested_skill 填 web(不要填 search',
'- 不确定时优先 agent_orchestration,避免漏执行',
'- 若 [Active Task] 显示上一轮 Agent 任务失败或进行中,用户当前消息是在纠正、补充、追问进度或要求按前文继续 → agent_orchestration,不要判成 direct_chat',
'- [Recent Conversation] 是对话摘要,当前消息可能引用上文(如「不对」「按我说的」「还是没有改」);结合上下文判断,不要孤立看当前一句',
'- 记忆线索只用于辅助判断本轮意图,不能替用户扩写新需求',
'',
skills.length ? `当前用户已授权 skills${skills.join(', ')}` : '当前用户未授权额外 skills。',
@@ -626,15 +721,83 @@ export function buildRouterContext(resolveResult, {
};
}
function buildRouterUserPrompt({ text, routerContext }) {
const context = String(routerContext ?? '').trim() || '无';
return [
'[Router Context]',
context,
'',
'[User]',
text || '(empty)',
].join('\n');
export function formatRouterTranscript(messages = [], {
maxTurns = 8,
maxChars = 4_000,
excludeLatestUserText = null,
} = {}) {
const normalizedExclude = String(excludeLatestUserText ?? '').trim();
const lines = [];
let usedChars = 0;
const visible = [];
for (const message of Array.isArray(messages) ? messages : []) {
const role = String(message?.role ?? '').trim();
if (role !== 'user' && role !== 'assistant') continue;
const raw = Array.isArray(message?.content)
? message.content
.map((item) => (typeof item === 'string' ? item : item?.text ?? ''))
.join('\n')
: String(message?.content ?? message?.text ?? '');
const displayText = String(message?.metadata?.displayText ?? '').trim();
const text = role === 'user'
? (displayText || deriveUserFacingText(raw))
: deriveAssistantFacingText(raw);
const trimmed = String(text ?? '').trim();
if (!trimmed) continue;
if (role === 'user' && normalizedExclude && trimmed === normalizedExclude) continue;
visible.push({ role, text: trimmed });
}
for (let index = visible.length - 1; index >= 0 && lines.length < maxTurns; index -= 1) {
const item = visible[index];
const line = `${item.role === 'user' ? '用户' : '助手'}${item.text}`;
if (lines.length > 0 && usedChars + line.length > maxChars) break;
lines.unshift(line);
usedChars += line.length;
}
return lines.join('\n');
}
function formatActiveTaskContextForRouter(activeTaskContext) {
if (!activeTaskContext?.hasRecentAgentTask) return '';
const parts = [
`- 上一轮 Agent 任务状态:${activeTaskContext.lastRunStatus ?? 'unknown'}`,
];
if (activeTaskContext.lastRunFailed) {
parts.push('- 上一轮任务失败,用户可能在纠正、补充或要求重试');
}
if (activeTaskContext.lastSuggestedSkill) {
parts.push(`- 上一轮建议 skill${activeTaskContext.lastSuggestedSkill}`);
}
if (activeTaskContext.lastIntentReason) {
parts.push(`- 上一轮路由原因:${activeTaskContext.lastIntentReason}`);
}
if (activeTaskContext.lastErrorMessage) {
parts.push(`- 失败摘要:${String(activeTaskContext.lastErrorMessage).slice(0, 200)}`);
}
return parts.join('\n');
}
function buildRouterUserPrompt({
text,
routerContext,
recentTranscript = '',
activeTaskContext = null,
} = {}) {
const sections = [];
const memoryContext = String(routerContext ?? '').trim();
if (memoryContext) {
sections.push('[Memory Context]', memoryContext, '');
}
const taskContext = formatActiveTaskContextForRouter(activeTaskContext);
if (taskContext) {
sections.push('[Active Task]', taskContext, '');
}
const transcript = String(recentTranscript ?? '').trim();
if (transcript) {
sections.push('[Recent Conversation]', transcript, '');
}
sections.push('[Current User Message]', text || '(empty)');
return sections.join('\n');
}
function parseRouterJson(reply) {
@@ -1043,6 +1206,8 @@ export function classifyWithRules({
sessionMessageCount = null,
userMessage = null,
includeIntentPatterns = true,
activeTaskContext = null,
grantedSkills = [],
} = {}) {
const decisionContext = {
text,
@@ -1123,6 +1288,21 @@ export function classifyWithRules({
reason: 'Agent 会话确认/续聊',
}, { source: 'rule' }), decisionContext);
}
if (
includeIntentPatterns
&& normalized
&& shouldForceActiveAgentTaskContinuation({
activeTaskContext,
text: normalized,
sessionId,
sessionMessageCount,
})
) {
return finalizeRouterClassification(
buildActiveTaskFollowUpClassification(activeTaskContext, { grantedSkills }),
decisionContext,
);
}
if (includeIntentPatterns && normalized && isPageDataDevIntent(normalized)) {
return finalizeRouterClassification(normalizeClassification({
route: CHAT_INTENT_ROUTE.AGENT,
@@ -1173,6 +1353,16 @@ export function classifyWithRules({
return finalizeRouterClassification(buildRealtimeInfoClassification(), decisionContext);
}
if (includeIntentPatterns && normalized) {
if (
shouldDeferActiveTaskRoutingToLlm({
activeTaskContext,
text: normalized,
sessionId,
sessionMessageCount,
})
) {
return null;
}
return finalizeRouterClassification(normalizeClassification({
route: CHAT_INTENT_ROUTE.DIRECT_CHAT,
confidence: 0.72,
@@ -1229,6 +1419,8 @@ export function createChatIntentRouter(options = {}) {
text,
grantedSkills = [],
routerContext = null,
recentTranscript = '',
activeTaskContext = null,
} = {}) {
if (typeof llmProviderService?.createChatCompletion !== 'function') return null;
try {
@@ -1244,6 +1436,8 @@ export function createChatIntentRouter(options = {}) {
content: buildRouterUserPrompt({
text,
routerContext: routerContext?.content ?? null,
recentTranscript,
activeTaskContext,
}),
},
],
@@ -1496,6 +1690,8 @@ export function createChatIntentRouter(options = {}) {
toolMode = 'chat',
forceDeepReasoning = false,
grantedSkills = [],
activeTaskContext = null,
recentTranscript = '',
} = {}) {
const text = messageDisplayText(userMessage);
const decisionContext = {
@@ -1535,10 +1731,28 @@ export function createChatIntentRouter(options = {}) {
sessionMessageCount,
userMessage,
includeIntentPatterns: true,
activeTaskContext,
grantedSkills,
});
if (ruleResult) return finalizeWithCoercion(ruleResult);
const baseline = buildFallbackClassification();
const activeTaskFallback = (
shouldForceActiveAgentTaskContinuation({
activeTaskContext,
text,
sessionId,
sessionMessageCount,
})
|| shouldDeferActiveTaskRoutingToLlm({
activeTaskContext,
text,
sessionId,
sessionMessageCount,
})
)
? buildActiveTaskFollowUpClassification(activeTaskContext, { grantedSkills })
: null;
const baseline = activeTaskFallback ?? buildFallbackClassification();
if (!llmRouterEligible) {
return finalizeWithCoercion(baseline);
}
@@ -1554,14 +1768,19 @@ export function createChatIntentRouter(options = {}) {
.catch(() => buildRouterContext(null))
: Promise.resolve(buildRouterContext(null));
// Memory is optional routing hint; run LLM in parallel to cut serial latency.
const [routerContext, llmResult] = await Promise.all([
memoryPromise,
const llmPromise = memoryPromise.then((routerContext) =>
classifyWithLlm({
text,
grantedSkills,
routerContext: null,
routerContext,
recentTranscript,
activeTaskContext,
}),
);
const [routerContext, llmResult] = await Promise.all([
memoryPromise,
llmPromise,
]);
if (policy.shadowMode) {
@@ -1592,7 +1811,7 @@ export function createChatIntentRouter(options = {}) {
if (!llmResult || llmResult.confidence < policy.minConfidence) {
return finalizeWithCoercion({
...baseline,
...(activeTaskFallback ?? baseline),
llmSuggestion: llmResult
? {
route: llmResult.route,