Remove curator fallback questions

This commit is contained in:
Sai-karthik
2026-06-15 09:30:29 +00:00
parent 9bb2c0de3f
commit 37fa8f13f4
3 changed files with 39 additions and 224 deletions

View File

@@ -94,25 +94,6 @@ async function enqueueVisibleText(controller: ReadableStreamDefaultController<Ui
}
}
function fallbackVisibleQuestion(body: z.infer<typeof chatSchema>) {
const service = (body.task.service ?? "").toLowerCase();
const routePath = body.task.route ? new URL(body.task.route, "https://growqr.local").pathname.toLowerCase() : "";
if (service.includes("resume") || routePath.includes("/agents/resume")) {
return "Could you share the resume text or upload the resume file, plus the target role you want these talking points for?";
}
if (service.includes("interview") || routePath.includes("/agents/interview")) {
return "What role and round should this interview prep focus on, and what is one thing you want to improve?";
}
if (service.includes("roleplay") || routePath.includes("/agents/roleplay")) {
return "What conversation scenario do you want to practice, who is the other person, and what outcome do you want?";
}
return `What should I capture for "${body.task.subtask}" so I can update this mission stage?`;
}
async function applyDailyMissionResult(input: {
userId: string;
body: z.infer<typeof chatSchema>;
@@ -293,13 +274,7 @@ export function dailyMissionRoutes() {
updateSummary: result.updateSummary ? sanitizeAssistantText(result.updateSummary) : result.updateSummary,
};
if (!result.reply.trim()) {
result = {
...result,
reply: fallbackVisibleQuestion(body),
completed: false,
updateSummary: undefined,
};
await enqueueVisibleText(controller, result.reply);
throw new Error("daily_mission_empty_model_reply");
}
const finalPayload = await applyDailyMissionResult({ userId, body, result });
controller.enqueue(sse("final", finalPayload));

View File

@@ -157,103 +157,6 @@ function curatorSystemAddendum(input: {
].join("\n");
}
function subtaskFallbackReply(input: {
subtask?: string;
subtaskIndex?: number;
task?: Awaited<ReturnType<typeof buildCuratorTasks>>[number];
latest: string;
history?: CuratorMessage[];
}) {
const subtask = input.subtask?.toLowerCase() ?? "";
const latest = input.latest.trim();
const hasUserAnswer = Boolean(latest && !/^start$/i.test(latest) && !/^i opened /i.test(latest));
const taskTitle = input.task?.title ?? "this mission";
const targetRole = targetRoleState(input.history ?? [], latest).targetRole;
if (subtask.includes("current role") || subtask.includes("background")) {
return hasUserAnswer
? "Got it. I will use that as your starting point. Next, open the target role task so I can lock the direction."
: "What are you doing now? Share your current role, years of experience, and the background I should factor in.";
}
if (subtask.includes("target role") || subtask.includes("role you want")) {
return hasUserAnswer
? `Got it, ${targetRole ?? latest} is the target direction. Next, add the constraints I should plan around.`
: "Which role are you aiming for next?";
}
if (subtask.includes("constraint") || subtask.includes("timeline") || subtask.includes("salary") || subtask.includes("location")) {
return hasUserAnswer
? "Saved. I now have enough direction to shape the next resume or interview handoff for this mission."
: "What constraints should I plan around, like timeline, location, salary, visa, remote preference, or industry?";
}
if (subtask.includes("resume")) {
return hasUserAnswer
? "Got it. I will use that resume context for the handoff instead of asking the broad setup again."
: "Paste the resume text or upload the file here, and I will read it against the mission goal.";
}
if (subtask.includes("interview")) {
return hasUserAnswer
? "Got it. I will use that to prepare the interview setup."
: "What role, interview round, and one weak spot should the interview setup focus on?";
}
if (Number.isInteger(input.subtaskIndex)) {
return hasUserAnswer
? `Got it. I saved that for ${taskTitle}.`
: `For ${input.subtask}, what detail should I use?`;
}
return undefined;
}
function fallbackReply(task: Awaited<ReturnType<typeof buildCuratorTasks>>[number] | undefined, latest: string, history: CuratorMessage[] = [], subtask?: string, subtaskIndex?: number) {
const subtaskReply = subtaskFallbackReply({ task, latest, history, subtask, subtaskIndex });
if (subtaskReply) return subtaskReply;
const lower = latest.toLowerCase();
const taskText = `${task?.title ?? ""} ${task?.subtitle ?? ""} ${task?.contextNarrative ?? ""} ${task?.subtasks.join(" ") ?? ""}`.toLowerCase();
const isTargetRoleTask = taskText.includes("target role") || taskText.includes("career transition") || taskText.includes("transition thesis");
if (isTargetRoleTask) {
const state = targetRoleState(history, latest);
if (!state.targetRole && (lower.includes("opened") || lower.includes("target role") || lower.includes("role you want"))) {
return "Which role are you aiming for next?";
}
if (!state.targetRole) return "What target role are you considering next?";
if (!state.currentBackground) return `Got it, ${state.targetRole} is the direction. What are you doing now, and what background should I factor in?`;
if (!state.constraints) return "Good, that gives me the direction and starting point. What constraints should I plan around, like timeline, location, salary, or industry?";
return "I have the target role, current background, and constraints. Should I shape the resume plan first or set up interview practice for this direction?";
}
if (taskText.includes("main goal") || taskText.includes("30 days") || taskText.includes("onboarding")) {
if (latest.trim().length > 1 && latest.trim().length < 120) {
return `Got it. What is blocking you from ${latest.trim()} right now, and which service should help first: resume, interview, or roleplay?`;
}
return "What outcome do you want in the next 30 days, and what is blocking you right now?";
}
if (task?.serviceId === "resume-service") {
if (lower.includes("target role") || lower.includes("goal")) {
return "What target role are you aiming for, and what kind of resume change would help most right now: stronger proof, clearer skills, or better role fit?";
}
if (lower.includes("upload") || lower.includes("paste") || lower.includes("resume")) {
return "Send the resume text or upload the file here. I will read it against your goal and prepare the resume handoff from that context.";
}
if (lower.includes("change")) {
return "Which part do you want changed first: summary, experience bullets, skills, projects, or role alignment?";
}
return "Tell me the target role first. Then I will ask for the resume and the exact changes you want.";
}
if (task?.serviceId === "interview-service") {
if (lower.includes("role") || lower.includes("round")) {
return "What role and interview round should this practice room be for?";
}
if (lower.includes("resume") || lower.includes("job")) {
return "Paste the job description or the resume section the interviewer should use as context.";
}
return "What is the one interview skill you want to improve in this setup?";
}
if (task?.serviceId === "roleplay-service") {
if (lower.includes("scenario")) return "What real conversation scenario should we practice?";
if (lower.includes("outcome") || lower.includes("tone")) return "What outcome do you want, and what tone should you practice?";
return "Tell me who you are speaking with and what makes this conversation difficult.";
}
return "Tell me where you are starting from and the outcome you want next. I will use that to choose the right service handoff.";
}
function curatorTaskKey(taskId?: string, subtaskIndex?: number) {
if (!taskId) return undefined;
return `${taskId}:${subtaskIndex ?? "task"}`;
@@ -266,10 +169,14 @@ function firstTurnPrompt(input: {
return [
`The user opened this focused subtask: ${input.subtask ?? input.task?.title ?? "curator task"}.`,
"Generate the first live conversational question for this exact subtask.",
"Ask only one question. Do not use fallback wording. Do not prepare any service handoff yet.",
"Ask only one question. Do not use canned wording. Do not prepare any service handoff yet.",
].join("\n");
}
function isExplicitHandoffRequest(text: string) {
return /\b(start|open|launch|begin|set up|setup|create|generate)\b/i.test(text);
}
async function evaluateSubtaskStatus(input: {
task?: Awaited<ReturnType<typeof buildCuratorTasks>>[number];
subtask?: string;
@@ -293,6 +200,7 @@ async function evaluateSubtaskStatus(input: {
"Use ready_to_capture only when the latest user answer directly satisfies the focused subtask.",
"Use needs_more_context if the assistant reply asks another question or if the answer is too vague for this exact subtask.",
"Use handoff_ready only when the focused subtask explicitly asks to open or preview a service and the service setup details are present.",
"Use handoff_ready when the user explicitly says to start, open, launch, set up, or begin the service and the necessary setup context is already present.",
"Never mark ready just because one message exists.",
"Use ASCII punctuation only.",
].join("\n"),
@@ -434,7 +342,7 @@ export async function runCuratorChat(input: {
throw error;
}
const statusUpdate = await evaluateSubtaskStatus({
let statusUpdate = await evaluateSubtaskStatus({
task,
subtask: input.subtask,
subtaskIndex: input.subtaskIndex,
@@ -442,6 +350,13 @@ export async function runCuratorChat(input: {
reply,
history: conversationHistory,
});
if (task?.serviceId && isExplicitHandoffRequest(latest)) {
statusUpdate = {
status: "handoff_ready",
summary: `${task.serviceName} handoff is ready.`,
confidence: Math.max(statusUpdate.confidence, 0.9),
};
}
if (statusUpdate.status !== "needs_more_context") {
if (reply.includes("?")) {

View File

@@ -102,99 +102,6 @@ function parseJsonObject(text: string) {
}
}
function fallbackTaskCopy(input: {
missionTitle: string;
stageTitle: string;
stageDescription: string;
serviceId?: CuratorServiceId;
}) {
const mission = input.missionTitle;
const stage = cleanTitle(input.stageTitle);
const intent = stageIntent(input);
if (input.serviceId === "resume-service") {
return {
title: stage.toLowerCase().includes("resume") ? stage : "Shape your resume around your goal",
subtitle: "Connect your target role, current resume, and the exact changes you want before opening the resume service.",
subtasks: [
"Tell the curator your target role and goal",
"Upload or paste your current resume",
"Pick the resume changes you want first",
],
contextNarrative: `This is a resume-first step for ${mission}. The curator is trying to understand what role you want, what your resume currently says, and what should change first. The resume service should only be opened after that context is captured, so the handoff can extract role-fit proof, gaps, and specific rewrite direction instead of asking the same broad question again.`,
};
}
if (input.serviceId === "interview-service") {
return {
title: stage.toLowerCase().includes("interview") ? stage : "Set up the right interview practice",
subtitle: "Tell the curator the role, round type, and one improvement focus before creating the interview room.",
subtasks: [
"Choose the target role and interview round",
"Add resume or job context for the interviewer",
"Preview the interview room setup",
],
contextNarrative: `This step prepares interview practice for ${mission}. The curator should collect the role, round type, and weakness to work on, then hand off to the interview service with enough context to create a useful preview. It is not a completion task by itself, and it should not mark progress until the interview service emits a real setup or review event.`,
};
}
if (input.serviceId === "roleplay-service") {
return {
title: stage.toLowerCase().includes("roleplay") ? stage : "Practice a real conversation scenario",
subtitle: "Define the situation, audience, and outcome before opening roleplay.",
subtasks: [
"Describe the conversation scenario",
"Set the desired outcome and tone",
"Open the roleplay practice with context",
],
contextNarrative: `This is a roleplay preparation step for ${mission}. The curator needs the conversation scenario, who the user is speaking with, and what outcome they want. The roleplay service should receive that setup and return practice feedback through service events.`,
};
}
if (input.serviceId === "matchmaking-service") {
return {
title: stage.toLowerCase().includes("role") || stage.toLowerCase().includes("path") ? stage : "Clarify your target direction",
subtitle: "Capture target role, constraints, and preferences so the next service action is relevant.",
subtasks: [
"Name the roles you are considering",
"Add constraints like location, salary, or timeline",
"Save the direction for the next service step",
],
contextNarrative: `This step is about direction before execution. The curator is collecting role preferences and constraints for ${mission}, so future resume, interview, and pathway suggestions can be grounded in what the user actually wants.`,
};
}
if (intent === "target-role") {
return {
title: "Clarify your target role",
subtitle: "Tell the curator where you are starting from, which role you want next, and what constraints matter.",
subtasks: [
"Share your current role or background",
"Name the target role you want next",
"Add constraints like timeline, location, or salary",
],
contextNarrative: `This is the direction-setting step for ${mission}. The curator should collect the user's current background, target role, reason for the transition, and constraints before opening resume, interview, or roleplay. The goal is to create a clear transition thesis, not to ask the same generic planning question repeatedly.`,
};
}
if (intent === "goal-setup") {
return {
title: "Tell the curator your main goal",
subtitle: "Start with the outcome you want, why it matters, and what help you need from GrowQR.",
subtasks: [
"Describe the outcome you want in 30 days",
"Add what is blocking you right now",
"Choose the first service that should help",
],
contextNarrative: `This is the goal setup step for ${mission}. The curator should understand what the user wants to achieve, what is blocking progress, and which GrowQR service should help first. It should turn the onboarding context into a useful next action, not a generic checklist.`,
};
}
return {
title: stage || "Clarify the next useful action",
subtitle: input.stageDescription || `Continue ${mission} with the next useful action.`,
subtasks: [
"Describe where you are starting from",
"Tell the outcome you want next",
"Confirm the best service to open",
],
contextNarrative: `This step belongs to ${mission}. The curator should ask for the user's starting point, desired outcome, and missing context so it can route to a useful service. Completion should come from a real platform or service event, not from simply clicking through the checklist.`,
};
}
async function taskCopy(input: {
missionTitle: string;
stageTitle: string;
@@ -233,14 +140,32 @@ async function taskCopy(input: {
`Stage description: ${input.stageDescription || "none"}`,
`Stage role: ${input.role ?? "none"}`,
`Service: ${input.serviceId ? serviceName(input.serviceId) : "Mission Planner"}`,
`Fallback intent: ${stageIntent(input)}`,
`Detected intent: ${stageIntent(input)}`,
].join("\n"),
});
const copy = generatedTaskCopySchema.parse(parseJsonObject(result.text));
let rawCopy = result.text;
let parsedCopy: unknown;
try {
parsedCopy = parseJsonObject(rawCopy);
} catch (parseError) {
const repaired = await generateText({
model: getConversationModel(),
system: [
"Repair invalid JSON into valid JSON only.",
"Do not add markdown.",
"Do not add or invent new task content.",
"Keep the exact object shape: {\"title\": string, \"subtitle\": string, \"subtasks\": [string, string, string], \"contextNarrative\": string}.",
].join("\n"),
prompt: rawCopy,
});
rawCopy = repaired.text;
parsedCopy = parseJsonObject(rawCopy);
}
const copy = generatedTaskCopySchema.parse(parsedCopy);
taskCopyCache.set(cacheKey, copy);
return copy;
} catch (error) {
console.warn("curator task copy generation failed; using fallback", {
console.warn("curator task copy generation failed", {
missionTitle: input.missionTitle,
stageTitle: input.stageTitle,
serviceId: input.serviceId,
@@ -369,12 +294,12 @@ export async function buildCuratorTasks(userId: string, date = todayIso()): Prom
}
if (tasks.length < 3) {
const fallbackModules = listMissionDefinitions().flatMap((mission) =>
const registryModules = listMissionDefinitions().flatMap((mission) =>
mission.modules.map((module) => ({ mission, module, serviceId: serviceFromRole(module.role, module.service) })),
).filter((item) => item.serviceId !== "qscore-service");
const seenServices = new Set(tasks.map((task) => task.serviceId).filter(Boolean));
for (const item of fallbackModules) {
for (const item of registryModules) {
if (tasks.length >= 3) break;
if (!item.serviceId) continue;
if (item.serviceId && seenServices.has(item.serviceId)) continue;
@@ -394,7 +319,7 @@ export async function buildCuratorTasks(userId: string, date = todayIso()): Prom
if (item.serviceId) seenServices.add(item.serviceId);
}
for (const item of fallbackModules) {
for (const item of registryModules) {
if (tasks.length >= 3) break;
if (tasks.some((task) => task.missionId === item.mission.missionId && task.stageId === item.module.id)) continue;
tasks.push(await taskFromStage({