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:
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user