8f91c52c67
Introduce help escalation persistence, cursor help worker processing, optional OpenAI-compatible chat bridge, and page delivery remediation. Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
4.5 KiB
JavaScript
172 lines
4.5 KiB
JavaScript
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,
|
|
});
|
|
},
|
|
};
|
|
}
|