Stabilize curator chat fallbacks
This commit is contained in:
@@ -279,6 +279,77 @@ function servicePreviewSummary(task: Awaited<ReturnType<typeof buildCuratorTasks
|
||||
return `${task.serviceName} handoff is ready.`;
|
||||
}
|
||||
|
||||
function fallbackCuratorReply(input: {
|
||||
latest: string;
|
||||
task?: Awaited<ReturnType<typeof buildCuratorTasks>>[number];
|
||||
subtask?: string;
|
||||
targetRole?: string;
|
||||
}) {
|
||||
const latest = input.latest.trim();
|
||||
const lowerTitle = input.task?.title.toLowerCase() ?? "";
|
||||
const lowerSubtask = input.subtask?.toLowerCase() ?? "";
|
||||
const role = fallbackCuratorRole(input.targetRole);
|
||||
|
||||
if ((input.task?.serviceId === "interview-service" || input.task?.serviceId === "roleplay-service") && !input.targetRole) {
|
||||
return "What role are you targeting?";
|
||||
}
|
||||
|
||||
if (/^start$/i.test(latest)) {
|
||||
if (input.task?.serviceId === "qscore-service") {
|
||||
return "Open your current Q Score and tell me which readiness signal looks weakest today.";
|
||||
}
|
||||
if (input.task?.serviceId === "resume-service") {
|
||||
return "Upload your current resume or paste three recent wins so I can anchor this proof task.";
|
||||
}
|
||||
if (input.task?.serviceId === "interview-service" || input.task?.serviceId === "roleplay-service") {
|
||||
return `I have your target role as ${role}. Say start when you want the preview opened.`;
|
||||
}
|
||||
if (lowerTitle.includes("role direction") || lowerSubtask.includes("role direction")) {
|
||||
return "Which role family do you want this sprint to optimize toward?";
|
||||
}
|
||||
if (input.task?.taskType === "measurement") {
|
||||
return "Open the current view and tell me the one gap or signal that stands out most.";
|
||||
}
|
||||
if (input.task?.taskType === "proof") {
|
||||
return "Share the strongest proof you already have so we can build from something real.";
|
||||
}
|
||||
return "What is the single outcome you want from this task today?";
|
||||
}
|
||||
|
||||
if (input.task?.serviceId === "interview-service" || input.task?.serviceId === "roleplay-service") {
|
||||
if (isExplicitHandoffRequest(latest)) {
|
||||
return servicePreviewSummary(input.task, input.targetRole);
|
||||
}
|
||||
return `Captured ${role} as the target role. Say start when you want the preview opened.`;
|
||||
}
|
||||
|
||||
if (lowerTitle.includes("role direction") || lowerSubtask.includes("role direction")) {
|
||||
return `Captured ${latest}. I will use that as the role direction for this sprint.`;
|
||||
}
|
||||
|
||||
if (input.task?.serviceId === "resume-service") {
|
||||
return "Captured. Open the resume flow when you are ready to turn this into proof.";
|
||||
}
|
||||
|
||||
if (input.task?.serviceId === "qscore-service") {
|
||||
return "Captured. Open the Q Score view and save the main readiness gap you want to work on.";
|
||||
}
|
||||
|
||||
if (input.task?.taskType === "measurement") {
|
||||
return "Captured the baseline signal for today.";
|
||||
}
|
||||
|
||||
if (input.task?.taskType === "proof") {
|
||||
return "Captured the proof point for today.";
|
||||
}
|
||||
|
||||
if (input.task?.taskType === "practice") {
|
||||
return "Captured the practice focus for today.";
|
||||
}
|
||||
|
||||
return "Captured. We can use this to move the task forward.";
|
||||
}
|
||||
|
||||
async function evaluateSubtaskStatus(input: {
|
||||
task?: Awaited<ReturnType<typeof buildCuratorTasks>>[number];
|
||||
subtask?: string;
|
||||
@@ -462,6 +533,7 @@ export async function runCuratorChat(input: {
|
||||
}
|
||||
|
||||
let reply = "";
|
||||
let usedFallbackReply = false;
|
||||
try {
|
||||
try {
|
||||
const extract = await generateText({
|
||||
@@ -523,7 +595,23 @@ export async function runCuratorChat(input: {
|
||||
subtask: input.subtask,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw error;
|
||||
reply = sanitize(fallbackCuratorReply({
|
||||
latest,
|
||||
task,
|
||||
subtask: input.subtask,
|
||||
targetRole,
|
||||
}));
|
||||
usedFallbackReply = true;
|
||||
}
|
||||
|
||||
if (!reply) {
|
||||
reply = sanitize(fallbackCuratorReply({
|
||||
latest,
|
||||
task,
|
||||
subtask: input.subtask,
|
||||
targetRole,
|
||||
}));
|
||||
usedFallbackReply = true;
|
||||
}
|
||||
|
||||
let statusUpdate = await evaluateSubtaskStatus({
|
||||
@@ -541,6 +629,12 @@ export async function runCuratorChat(input: {
|
||||
confidence: Math.max(statusUpdate.confidence, 0.9),
|
||||
};
|
||||
}
|
||||
if (usedFallbackReply && statusUpdate.status === "needs_more_context" && !statusUpdate.nextMissingInfo) {
|
||||
statusUpdate = {
|
||||
...statusUpdate,
|
||||
summary: reply,
|
||||
};
|
||||
}
|
||||
if (isPreviewHandoffService(task) && !isInitialOpen && usefulUserMessages(conversationHistory).length >= 1) {
|
||||
statusUpdate = {
|
||||
status: "handoff_ready",
|
||||
|
||||
@@ -71,9 +71,27 @@ function roleFromTask(task?: CuratorTask) {
|
||||
export function inferRoleFromText(text?: string) {
|
||||
const value = text?.trim();
|
||||
if (!value) return undefined;
|
||||
if (value.length <= 80 && ROLE_PATTERN.test(value)) return value.replace(/[.?!]+$/, "").trim();
|
||||
const explicit = value.match(/(?:targeting|for|as|role is|role:)\s+([A-Za-z][A-Za-z0-9 +/&.-]{2,60})/i)?.[1];
|
||||
return explicit?.replace(/[.?!]+$/, "").trim();
|
||||
const cleanRole = (raw?: string) => {
|
||||
if (!raw) return undefined;
|
||||
const normalized = raw
|
||||
.replace(/^(?:i am |i'm |im )/i, "")
|
||||
.replace(/^(?:targeting|aiming for|looking for)\s+/i, "")
|
||||
.replace(/[.?!,]+$/, "")
|
||||
.replace(/\broles?\b/gi, "")
|
||||
.replace(/\b(this|next)\s+(week|month|quarter|year)\b/gi, "")
|
||||
.replace(/\b(right now|currently)\b/gi, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (!normalized) return undefined;
|
||||
if (/^pm$/i.test(normalized)) return "Product Manager";
|
||||
if (/^swe$/i.test(normalized)) return "Software Engineer";
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const explicit = value.match(/(?:targeting|for|as|toward|towards|role is|role:)\s+([A-Za-z][A-Za-z0-9 +/&.-]{2,60}?)(?=\s+roles?\b|\s+(?:this|next)\s+(?:week|month|quarter|year)\b|[.?!,]|$)/i)?.[1];
|
||||
if (explicit) return cleanRole(explicit);
|
||||
if (value.length <= 80 && ROLE_PATTERN.test(value)) return cleanRole(value);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function resolveCuratorTargetRole(input: {
|
||||
|
||||
Reference in New Issue
Block a user