Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 109 additions & 97 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function checkForClearAffirmation(text: string): boolean {

// Check if user is requesting to start/continue the reading (not OSR)
function checkForReadingStartRequest(text: string): boolean {
const lower = text.toLowerCase();
const lower = text.toLowerCase().trim();
const startReadingPhrases = [
'give me the reading',
'start the reading',
Expand All @@ -107,9 +107,13 @@ function checkForReadingStartRequest(text: string): boolean {
'let\'s start',
'please continue',
'go ahead',
'proceed'
'proceed',
'continue',
'okay',
'yes',
'read it'
Comment on lines +111 to +114
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding generic trigger phrases like 'continue', 'okay', 'yes', and 'read it' to checkForReadingStartRequest creates potential issues:

  1. Overlap with affirmation detection: These phrases (especially 'yes') are also checked in checkForClearAffirmation (line 83), creating ambiguity about which check should take precedence.

  2. Global classification impact: Since classifyUserResponse() calls this function first (line 175, outside this diff), these generic phrases will now be classified as reading-start requests in ALL contexts, not just after a report is ready. This means any "yes" or "okay" response during a conversation will be treated as a CLEAR_WB response, which may not be the intended behavior.

Recommendation: Consider creating a separate list of trigger phrases specifically for the post-report-ready context (lines 356-358), keeping only the more specific phrases like 'start the reading', 'give me the reading', etc. in the shared checkForReadingStartRequest function. This would preserve the existing conversation flow while fixing the report-ready trigger issue.

Suggested change
'continue',
'okay',
'yes',
'read it'
// 'continue', // Removed: too generic
// 'okay', // Removed: too generic
// 'yes', // Removed: handled by affirmation
// 'read it' // Removed: too generic

Copilot uses AI. Check for mistakes.
];

return startReadingPhrases.some(phrase => lower.includes(phrase));
}

Expand Down Expand Up @@ -345,41 +349,49 @@ Give a short, plain-language summary of the current planetary weather in two par
return new Response(responseBody, { headers: { 'Content-Type': 'text/plain; charset=utf-8' }});
}

// Check for natural follow-up flow based on user response type
//
// DESIGN NOTE: Skip OSR checks on first turn UNLESS explicit OSR phrases used
// Rationale: First turn after "Session Started" is typically a command, not a resonance response.
// However, we still detect explicit OSR phrases like "doesn't resonate" for edge cases.
//
// The OSR check is duplicated here (before classifyUserResponse) intentionally:
// - Early exit for first-turn commands (performance optimization)
// - Preserves classification logic independence in classifyUserResponse()
// - Makes the first-turn special case explicit and easy to understand
const skipOSRCheck = isFirstTurn && !checkForOSRIndicators(text);
const responseType = skipOSRCheck
? 'CLEAR_WB'
: classifyUserResponse(text);

// Mock session context (in production, this would be persisted)
const mockSessionContext: SessionContext = {
wbHits: [],
abeHits: [],
osrMisses: [],
actorWeighting: 0,
roleWeighting: 0,
driftIndex: 0,
sessionActive: true
};

// Generate natural follow-up based on response type
if (responseType === 'CLEAR_WB') {
const followUp = naturalFollowUpFlow.generateFollowUp({
type: 'AFFIRM',
content: text,
originalMirror: text
}, mockSessionContext);

analysisPrompt = `The user clearly confirmed resonance: "${text}"
const lastRavenMessage = messages.filter((m: any) => m.role === 'raven').pop();
const isAfterReportReady = lastRavenMessage?.html?.includes('is ready for interpretation');
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detection mechanism lastRavenMessage?.html?.includes('is ready for interpretation') is fragile as it relies on an exact substring match of the UI message text. If the message wording changes in the future (e.g., "is ready to interpret", "ready for reading"), this check will silently fail.

Consider using a more robust detection mechanism, such as:

  1. Adding a message metadata flag (e.g., messageType: 'report-ready')
  2. Checking for a specific state/phase indicator
  3. Using a regex pattern that's more flexible
Suggested change
const isAfterReportReady = lastRavenMessage?.html?.includes('is ready for interpretation');
// Prefer robust detection: use messageType if present, else fallback to regex on html
const isAfterReportReady =
lastRavenMessage?.messageType === 'report-ready' ||
(typeof lastRavenMessage?.html === 'string' &&
/ready\s*(for|to)?\s*(interpret|interpretation|reading)/i.test(lastRavenMessage.html));

Copilot uses AI. Check for mistakes.
const isTriggerForReading = checkForReadingStartRequest(text);

if (isAfterReportReady && isTriggerForReading) {
analysisPrompt = `CONTEXT: The following chart data has been provided. Use it to generate a complete, conversational mirror reflection following the Five-Step Delivery Framework.
INSTRUCTIONS: Begin with warm recognition of the person's stance/pattern. Use the chart geometry as context, but write in natural, conversational paragraphs. Follow the FIELD→MAP→VOICE protocol. No technical openings, no data summaries—just the warm, direct mirror.`;
} else {
// Check for natural follow-up flow based on user response type
//
// DESIGN NOTE: Skip OSR checks on first turn UNLESS explicit OSR phrases used
// Rationale: First turn after "Session Started" is typically a command, not a resonance response.
// However, we still detect explicit OSR phrases like "doesn't resonate" for edge cases.
//
// The OSR check is duplicated here (before classifyUserResponse) intentionally:
// - Early exit for first-turn commands (performance optimization)
// - Preserves classification logic independence in classifyUserResponse()
// - Makes the first-turn special case explicit and easy to understand
const skipOSRCheck = isFirstTurn && !checkForOSRIndicators(text);
const responseType = skipOSRCheck
? 'CLEAR_WB'
: classifyUserResponse(text);

// Mock session context (in production, this would be persisted)
const mockSessionContext: SessionContext = {
wbHits: [],
abeHits: [],
osrMisses: [],
actorWeighting: 0,
roleWeighting: 0,
driftIndex: 0,
sessionActive: true
};

// Generate natural follow-up based on response type
if (responseType === 'CLEAR_WB') {
const followUp = naturalFollowUpFlow.generateFollowUp({
type: 'AFFIRM',
content: text,
originalMirror: text
}, mockSessionContext);

Comment on lines +388 to +393
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable followUp.

Suggested change
const followUp = naturalFollowUpFlow.generateFollowUp({
type: 'AFFIRM',
content: text,
originalMirror: text
}, mockSessionContext);

Copilot uses AI. Check for mistakes.
analysisPrompt = `The user clearly confirmed resonance: "${text}"

**AUTO-CLASSIFICATION: WB (Within Boundary)**
Log this as confirmed resonance. Do NOT ask for additional validation.
Expand All @@ -399,60 +411,59 @@ Your response should:
4. Skip any additional truth gates

User's clear affirmation: ${text}`;
} else if (responseType === 'PARTIAL_ABE') {
analysisPrompt = `The user gave partial confirmation: "${text}"

} else if (responseType === 'PARTIAL_ABE') {
analysisPrompt = `The user gave partial confirmation: "${text}"

**CLASSIFICATION: ABE (At Boundary Edge)**
This needs clarification, not full repair. Ask for refinement:

"I'm logging this as ABE—partially resonant but needs fine-tuning. What part lands, and what feels off?"

User's partial response: ${text}`;
} else if (responseType === 'OSR') {
const followUp = naturalFollowUpFlow.generateFollowUp({
type: 'OSR',
content: text,
originalMirror: text
}, mockSessionContext);
analysisPrompt = `The user indicated that something didn't resonate. Generate a response that includes this natural OSR probe: "${followUp.question}"

} else if (responseType === 'OSR') {
const followUp = naturalFollowUpFlow.generateFollowUp({
type: 'OSR',
content: text,
originalMirror: text
}, mockSessionContext);

analysisPrompt = `The user indicated that something didn't resonate. Generate a response that includes this natural OSR probe: "${followUp.question}"

User's OSR response: ${text}

Your response should acknowledge their feedback and offer the choice-based clarification probe to convert the miss into diagnostic data. Keep it skippable and non-forcing.`;
} else if (text.toLowerCase().includes('poetic card') || text.toLowerCase().includes('generate card')) {
analysisPrompt = `The user is requesting a poetic card based on their session. Generate a visual card display showing:

} else if (text.toLowerCase().includes('poetic card') || text.toLowerCase().includes('generate card')) {
analysisPrompt = `The user is requesting a poetic card based on their session. Generate a visual card display showing:
- Resonance Pattern summary
- Score indicators (WB/ABE/OSR)
- Actor/Role composite guess
- Any drift flags
Do NOT generate a new poem. This is a summary card of what has already resonated.`;
} else if (text.toLowerCase().includes('done') || text.toLowerCase().includes('finished') || text.toLowerCase().includes('session complete')) {
const closure = naturalFollowUpFlow.generateSessionClosure();
analysisPrompt = `The user is indicating they want to end this reading session. Generate a response that includes: "${closure.resetPrompt}"

} else if (text.toLowerCase().includes('done') || text.toLowerCase().includes('finished') || text.toLowerCase().includes('session complete')) {
const closure = naturalFollowUpFlow.generateSessionClosure();
analysisPrompt = `The user is indicating they want to end this reading session. Generate a response that includes: "${closure.resetPrompt}"

This will reset the scorecard but not make you forget who you're talking to. Offer these options: ${closure.continuationOptions.join(', ')}`;
} else {
const lastRavenMessage = messages.filter((m: any) => m.role === 'raven').pop();
const isResponseToProbe = lastRavenMessage &&
(lastRavenMessage.html.includes('Does any of this feel familiar') ||
lastRavenMessage.html.includes('Does this fit your experience') ||
lastRavenMessage.html.includes('feel accurate') ||
lastRavenMessage.html.includes('resonate'));

if (isResponseToProbe) {
if (isMetaSignalAboutRepetition(text)) {
const reversed = [...messages].filter((m:any)=> m.role==='user').reverse();
const previousUser = reversed[1];
const prevText = previousUser?.content || previousUser?.html || '';
const prevType = prevText ? classifyUserResponse(prevText) : 'UNCLEAR';

if (prevType === 'CLEAR_WB') {
analysisPrompt = `The user expressed irritation at being asked again (meta-signal), not new content: "${text}".
} else {
const isResponseToProbe = lastRavenMessage &&
(lastRavenMessage.html.includes('Does any of this feel familiar') ||
lastRavenMessage.html.includes('Does this fit your experience') ||
lastRavenMessage.html.includes('feel accurate') ||
lastRavenMessage.html.includes('resonate'));

if (isResponseToProbe) {
if (isMetaSignalAboutRepetition(text)) {
const reversed = [...messages].filter((m: any) => m.role === 'user').reverse();
const previousUser = reversed[1];
const prevText = previousUser?.content || previousUser?.html || '';
const prevType = prevText ? classifyUserResponse(prevText) : 'UNCLEAR';

if (prevType === 'CLEAR_WB') {
analysisPrompt = `The user expressed irritation at being asked again (meta-signal), not new content: "${text}".

Preserve the prior classification: WB (Within Boundary). Do NOT re-open validation.

Expand All @@ -465,30 +476,30 @@ Rules:
1) No additional "does this feel true?" gates
2) No motive analysis or personality inference
3) Mirror only structural pressure and pivot to somatic/behavioral deepening`;
} else if (prevType === 'PARTIAL_ABE') {
analysisPrompt = `The user commented on repetition (meta-signal), not new content: "${text}".
} else if (prevType === 'PARTIAL_ABE') {
analysisPrompt = `The user commented on repetition (meta-signal), not new content: "${text}".

Preserve prior classification: ABE (At Boundary Edge). Do NOT re-open the main validation gate.

Respond with: Acknowledge the irritation + offer one focused refinement question about what part lands vs. what doesn't, using their words where possible.

Rules: no new metaphors, no psychoanalysis, keep it brief and user-led.`;
} else if (prevType === 'OSR') {
analysisPrompt = `The user commented on repetition (meta-signal): "${text}".
} else if (prevType === 'OSR') {
analysisPrompt = `The user commented on repetition (meta-signal): "${text}".

Preserve prior classification: OSR (Outside Symbolic Range). Do NOT analyze the meta-comment. Offer a minimal repair that uses their prior correction, then validate the repair only if they choose to engage.

Keep it skippable and brief; acknowledge the repetition irritation.`;
} else {
analysisPrompt = `Treat this as a meta-signal about repetition: "${text}".
} else {
analysisPrompt = `Treat this as a meta-signal about repetition: "${text}".

Do not analyze it. Briefly acknowledge the irritation and ask one gentle, concrete deepening question about the previously discussed pressure (without re-validating).`;
}
} else {
const probeResponseType = classifyUserResponse(text);
if (probeResponseType === 'CLEAR_WB') {
analysisPrompt = `The user clearly confirmed resonance to your probe: "${text}"
}
} else {
const probeResponseType = classifyUserResponse(text);

if (probeResponseType === 'CLEAR_WB') {
analysisPrompt = `The user clearly confirmed resonance to your probe: "${text}"

**AUTO-CLASSIFICATION: WB (Within Boundary)**
This is confirmed resonance. Log it immediately without additional validation.
Expand All @@ -505,9 +516,9 @@ This is confirmed resonance. Log it immediately without additional validation.
**DO NOT** ask "Does this feel true?" or any additional validation. The user already confirmed it.

User's clear confirmation: "${text}"`;
} else if (probeResponseType === 'PARTIAL_ABE') {
analysisPrompt = `The user gave partial confirmation to your probe: "${text}"

} else if (probeResponseType === 'PARTIAL_ABE') {
analysisPrompt = `The user gave partial confirmation to your probe: "${text}"

**CLASSIFICATION: ABE (At Boundary Edge)**
This needs refinement, not full repair.
Expand All @@ -518,9 +529,9 @@ This needs refinement, not full repair.
3. Refine the image based on their feedback

User's partial response: "${text}"`;
} else if (probeResponseType === 'OSR') {
analysisPrompt = `The user redirected/contradicted your probe: "${text}"

} else if (probeResponseType === 'OSR') {
analysisPrompt = `The user redirected/contradicted your probe: "${text}"

**CLASSIFICATION: OSR (Outside Symbolic Range)**
This requires a repair branch with validation.
Expand All @@ -532,9 +543,9 @@ This requires a repair branch with validation.
4. Validate REPAIR only: "Does this repair feel true?" [Yes] [Partly] [No]

User's OSR response: "${text}"`;
} else {
analysisPrompt = `The user gave an unclear response to your probe: "${text}"

} else {
analysisPrompt = `The user gave an unclear response to your probe: "${text}"

**CLASSIFICATION: UNCLEAR**
This needs gentle clarification to determine WB/ABE/OSR.
Expand All @@ -543,9 +554,9 @@ This needs gentle clarification to determine WB/ABE/OSR.
Ask for clarification: "I want to make sure I'm tracking you—does the image I offered feel familiar, or does it miss the mark?"

User's unclear response: "${text}"`;
}
}
}
} else {
} else {
analysisPrompt = `This appears to be a request for astrological insight or general conversation that could benefit from symbolic reflection.

**MANDATORY: Deliver COMPLETE Core Flow structure in your response:**
Expand All @@ -564,6 +575,7 @@ SESSION FLAG: FIRST_TURN = ${isFirstTurn ? 'TRUE' : 'FALSE'}
"Does any of this feel familiar?"

User's input: "${text}"`;
}
}
}

Expand Down
Loading