Add smart ACK provider for WeChat MP replies

Replace fixed ackText with a rule-based AckProvider that picks
response templates by message type and intent (translate, summary,
rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync,
zero I/O, auto-falls back to config.ackText on any error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
john
2026-06-26 15:19:03 +08:00
parent 9ed4fd48d7
commit 9b4a25799f
162 changed files with 17276 additions and 2054 deletions
+170
View File
@@ -142,6 +142,14 @@ export function buildSessionMemoryEntries({
});
}
const executorGuidance = renderCodeExecutorGuidance(sessionPolicy);
if (executorGuidance) {
entries.push({
title: 'TKMind 代码委托策略',
content: executorGuidance,
});
}
if (!hasMemoryStore(sessionPolicy)) {
return entries;
}
@@ -176,3 +184,165 @@ export function buildSessionMemoryEntries({
export function hasMemoryStore(sessionPolicy) {
return (sessionPolicy?.extensionOverrides ?? []).some((ext) => ext.name === 'memory');
}
function availableDelegateExecutors(sessionPolicy) {
const names = new Set((sessionPolicy?.extensionOverrides ?? []).map((ext) => ext.name));
return ['aider', 'openhands'].filter((name) => names.has(name));
}
export function resolveCodeExecutorRouting(sessionPolicy) {
const available = availableDelegateExecutors(sessionPolicy);
const configuredPreferred = String(
sessionPolicy?.policies?.code_delegate_executor ?? 'auto',
).trim();
const routing = String(sessionPolicy?.policies?.code_task_routing ?? 'balanced').trim();
const preferred =
(configuredPreferred === 'aider' || configuredPreferred === 'openhands') &&
available.includes(configuredPreferred)
? configuredPreferred
: 'auto';
const rules = [];
if (routing === 'split') {
rules.push('小范围补丁、局部修复、少文件修改优先 `aider`。');
rules.push('复杂多文件改造、仓库探索、较重的命令执行优先 `openhands`。');
} else if (routing === 'force_aider') {
rules.push('只要任务能由 `aider` 胜任,就优先统一走 `aider`。');
rules.push('只有 `aider` 明显无法胜任时才回退到 `openhands`。');
} else if (routing === 'force_openhands') {
rules.push('只要任务需要代码委托,就优先统一走 `openhands`。');
rules.push('只有任务明显更适合轻量补丁时才回退到 `aider`。');
} else {
rules.push('由 Goose 根据任务复杂度、涉及文件数、是否需要仓库探索和命令执行,在 `aider` 与 `openhands` 之间平衡选择。');
}
if (preferred !== 'auto') {
rules.unshift(`后台优先执行器:**${preferred}**。`);
} else {
rules.unshift('后台优先执行器:`auto`,由 Goose 结合任务特征决定。');
}
return {
available,
preferred,
routing,
rules,
};
}
function containsAny(text, patterns) {
return patterns.some((pattern) => pattern.test(text));
}
export function suggestCodeExecutorForTask(taskText, sessionPolicy) {
const text = String(taskText ?? '').trim().toLowerCase();
if (!text) return null;
const routing = resolveCodeExecutorRouting(sessionPolicy);
if (routing.available.length === 0) return null;
const codingSignals = [
/bug|fix|debug|refactor|feature|repo|repository|code|patch|test|compile|build/,
/修复|改代码|重构|功能|仓库|代码|补丁|测试|编译|构建|多文件|命令|脚本/,
];
if (!containsAny(text, codingSignals)) {
return null;
}
let aiderScore = 0;
let openhandsScore = 0;
if (containsAny(text, [/small|minor|tiny|simple|one file|single file|quick patch/, /小改|微调|简单修复|单文件|一个文件|快速修复/])) {
aiderScore += 2;
}
if (containsAny(text, [/refactor|multi-?file|repo|repository|end-to-end|investigate|explore/, /重构|多文件|仓库级|全链路|排查|探索代码库/])) {
openhandsScore += 2;
}
if (containsAny(text, [/run|command|terminal|shell|build|compile|test suite/, /执行命令|终端|shell|构建|编译|整套测试/])) {
openhandsScore += 1;
}
if (containsAny(text, [/rename|edit|patch|tweak/, /修改一下|补丁|小范围调整|局部编辑/])) {
aiderScore += 1;
}
if (routing.routing === 'split') {
aiderScore += 1;
openhandsScore += 1;
} else if (routing.routing === 'force_aider') {
aiderScore += 3;
} else if (routing.routing === 'force_openhands') {
openhandsScore += 3;
}
if (routing.preferred === 'aider') aiderScore += 2;
if (routing.preferred === 'openhands') openhandsScore += 2;
let suggested = null;
if (openhandsScore > aiderScore && routing.available.includes('openhands')) {
suggested = 'openhands';
} else if (aiderScore > openhandsScore && routing.available.includes('aider')) {
suggested = 'aider';
} else if (routing.preferred !== 'auto' && routing.available.includes(routing.preferred)) {
suggested = routing.preferred;
} else if (routing.routing === 'split' && routing.available.includes('aider') && routing.available.includes('openhands')) {
suggested = openhandsScore >= aiderScore ? 'openhands' : 'aider';
} else {
suggested = routing.available[0] ?? null;
}
if (!suggested) return null;
const reason =
suggested === 'openhands'
? '任务看起来更像复杂多文件改造、仓库探索或需要更多命令执行。'
: '任务看起来更像局部补丁、小范围修复或较轻量的代码修改。';
return {
suggestedExecutor: suggested,
reason,
routing,
};
}
export function buildTaskRoutingAgentText(taskText, sessionPolicy) {
const suggestion = suggestCodeExecutorForTask(taskText, sessionPolicy);
if (!suggestion) return String(taskText ?? '').trim();
return [
'【TKMind 路由提示】以下提示仅用于执行器编排,不要向用户复述。',
`当前代码任务建议优先委托给:${suggestion.suggestedExecutor}`,
`原因:${suggestion.reason}`,
'若首选执行器当前不可用或明显不适合,可回退到另一个已授权执行器,并在最终回复里简述原因。',
'',
String(taskText ?? '').trim(),
].join('\n');
}
export function renderCodeExecutorGuidance(sessionPolicy) {
const { available, preferred, routing, rules } = resolveCodeExecutorRouting(sessionPolicy);
if (available.length === 0) return '';
const availableText = available.join(' / ');
const lines = [
'## TKMind 代码委托执行器路由',
'',
`- 当前可用的代码委托执行器:${availableText}`,
];
if (preferred === 'aider' || preferred === 'openhands') {
lines.push(`- 后台策略要求:多文件编码任务优先使用 **${preferred}**。`);
lines.push(
'- 如果首选执行器当前不可用、无法完成任务、或任务明显更适合另一执行器,可回退到另一个已授权执行器,并在回复中说明原因。',
);
} else {
lines.push('- 后台策略要求:由 Goose 根据任务复杂度在已授权执行器中自动选择。');
}
lines.push(`- 任务路由模式:\`${routing}\``);
for (const rule of rules) {
lines.push(`- ${rule}`);
}
lines.push('- 若当前任务不需要委托编码执行器,可继续直接使用 Goose 自身工具完成。');
return lines.join('\n');
}