feat(help): add cursor help escalation worker and chat bridge
Introduce help escalation persistence, cursor help worker processing, optional OpenAI-compatible chat bridge, and page delivery remediation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
HELP_ESCALATION_STATUS,
|
||||
createHelpEscalationService,
|
||||
deliverHelpEscalationResult,
|
||||
parseHelpEscalationAgentResult,
|
||||
resolveCursorHelpAgentBin,
|
||||
resolveCursorHelpWorkspace,
|
||||
runCursorHelpAgent,
|
||||
} from './help-escalation.mjs';
|
||||
import {
|
||||
runHelpPageDeliveryRemediationScript,
|
||||
shouldAutoRemediateHelpViaGoose,
|
||||
} from './help-page-delivery-remediation.mjs';
|
||||
|
||||
function envFlag(value, fallback = false) {
|
||||
const raw = String(value ?? '').trim().toLowerCase();
|
||||
if (!raw) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||||
}
|
||||
|
||||
export function isCursorHelpWorkerEnabled(env = process.env) {
|
||||
return envFlag(env.MEMIND_CURSOR_HELP_WORKER_ENABLED, false);
|
||||
}
|
||||
|
||||
export async function processOneHelpEscalation({
|
||||
helpEscalationService,
|
||||
scheduleService = null,
|
||||
sendWechatTextToUser = null,
|
||||
logger = console,
|
||||
env = process.env,
|
||||
workerId = 'cursor-help-worker',
|
||||
} = {}) {
|
||||
const escalation = await helpEscalationService.claimNext(workerId);
|
||||
if (!escalation) return null;
|
||||
|
||||
const workspace = resolveCursorHelpWorkspace(env, helpEscalationService.memindRoot);
|
||||
const agentBin = resolveCursorHelpAgentBin(env);
|
||||
const prompt = helpEscalationService.buildPrompt(escalation);
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await runCursorHelpAgent({
|
||||
prompt,
|
||||
workspace,
|
||||
agentBin,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
const parsed = parseHelpEscalationAgentResult(stdout);
|
||||
let resultText = parsed.userMessage;
|
||||
let agentOutput = stdout;
|
||||
|
||||
if (
|
||||
parsed.status === HELP_ESCALATION_STATUS.SUCCEEDED
|
||||
&& shouldAutoRemediateHelpViaGoose(escalation, env)
|
||||
) {
|
||||
try {
|
||||
const remediated = await runHelpPageDeliveryRemediationScript({
|
||||
escalation,
|
||||
env,
|
||||
logger,
|
||||
viaGoose: true,
|
||||
});
|
||||
if (remediated?.userMessage) {
|
||||
resultText = remediated.userMessage;
|
||||
agentOutput = `${stdout}\n\n--- auto goose remediation ---\n${JSON.stringify(remediated)}`;
|
||||
}
|
||||
} catch (remediationError) {
|
||||
logger.warn?.(
|
||||
'[cursor-help] auto goose remediation failed:',
|
||||
remediationError instanceof Error ? remediationError.message : remediationError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const completed = await helpEscalationService.complete(escalation.id, {
|
||||
status: parsed.status,
|
||||
resultText,
|
||||
agentOutput,
|
||||
errorMessage: parsed.status === HELP_ESCALATION_STATUS.FAILED
|
||||
? stderr || parsed.userMessage
|
||||
: null,
|
||||
});
|
||||
await deliverHelpEscalationResult({
|
||||
escalation: completed,
|
||||
scheduleService,
|
||||
sendWechatTextToUser,
|
||||
logger,
|
||||
});
|
||||
return completed;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const completed = await helpEscalationService.complete(escalation.id, {
|
||||
status: HELP_ESCALATION_STATUS.FAILED,
|
||||
resultText: '工程师处理失败,请稍后重试或联系平台管理员。',
|
||||
agentOutput: error?.stdout ?? null,
|
||||
errorMessage: message,
|
||||
});
|
||||
await deliverHelpEscalationResult({
|
||||
escalation: completed,
|
||||
scheduleService,
|
||||
sendWechatTextToUser,
|
||||
logger,
|
||||
});
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
|
||||
export function startCursorHelpWorker({
|
||||
pool,
|
||||
scheduleService = null,
|
||||
sendWechatTextToUser = null,
|
||||
logger = console,
|
||||
env = process.env,
|
||||
pollMs = Number(env.MEMIND_CURSOR_HELP_POLL_MS ?? 2000),
|
||||
runOnStart = true,
|
||||
} = {}) {
|
||||
const helpEscalationService = createHelpEscalationService({ pool, logger, env });
|
||||
if (!helpEscalationService.enabled || !isCursorHelpWorkerEnabled(env)) {
|
||||
return { stop() {}, helpEscalationService };
|
||||
}
|
||||
|
||||
let stopped = false;
|
||||
let running = false;
|
||||
let timer = null;
|
||||
|
||||
const tick = async () => {
|
||||
if (stopped || running) return;
|
||||
running = true;
|
||||
try {
|
||||
await processOneHelpEscalation({
|
||||
helpEscalationService,
|
||||
scheduleService,
|
||||
sendWechatTextToUser,
|
||||
logger,
|
||||
env,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error?.('[cursor-help] worker tick failed:', error);
|
||||
} finally {
|
||||
running = false;
|
||||
}
|
||||
};
|
||||
|
||||
timer = setInterval(() => {
|
||||
void tick();
|
||||
}, pollMs);
|
||||
timer.unref?.();
|
||||
|
||||
if (runOnStart) {
|
||||
void tick();
|
||||
}
|
||||
|
||||
logger.log?.('[cursor-help] worker enabled');
|
||||
|
||||
return {
|
||||
helpEscalationService,
|
||||
stop() {
|
||||
stopped = true;
|
||||
if (timer) clearInterval(timer);
|
||||
},
|
||||
async processOnce() {
|
||||
return processOneHelpEscalation({
|
||||
helpEscalationService,
|
||||
scheduleService,
|
||||
sendWechatTextToUser,
|
||||
logger,
|
||||
env,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user