/**
* insight-orchestrator.js
*
* Architecture Orchestrator
* ————————-
* Coordinates Layer A, OutOfStep, Layer B, Layer C and Layer D.
*
* Layer A owns Insight eligibility/routing and framework intelligence:
* – A1 decides whether the reply remains within the current Insight and
* handles OutOfStep / non-resonance / relationship routes;
* – Layer A #1 selects the earliest current-or-forward coaching destination;
* – comprehensive Layer A #2 maintains authoritative framework coverage.
*/

(function () {
‘use strict’;

/**
* Build a readable debug trace from the decisions returned by each layer.
*/
function buildDecisionTrace(items) {
if (!items || !items.length) return ”;

const lines = [‘AI DECISION TRACE’, ‘Layered AI Coach’, ”];

items.forEach(item => {
lines.push(`Layer ${item.layer || ”}`);
lines.push(`Decision : ${item.decision || item.route || ”}`);
lines.push(`Result : ${item.value || ”}`);
lines.push(`Reason : ${item.reason || ”}`);

if (Array.isArray(item.details) && item.details.length) {
lines.push(”);

item.details.forEach(detail => {
lines.push(
`${detail.decision || detail.route || ”} ${detail.value || ”}${
detail.reason ? ` — ${detail.reason}` : ”
}`
);
});
}

lines.push(”);
});

return lines.join(‘\n’).trim();
}

/**
* Adopt the typing-time Layer A #1 destination batch only when it
* represents the exact response that has now been submitted.
*
* Keep the original batch object and promise intact so Send can wait on
* work that is already in flight rather than restarting it.
*/
function getExactSubmittedDestinationPromise(ctx) {
const batch =
ctx.submittedResponseSession?.destinationBatch;

if (!batch || typeof batch !== ‘object’) {
return null;
}

const submittedText =
String(ctx.userText || ”).trim();

const checkpointText =
String(batch.checkpointText || ”).trim();

const submittedVersion =
Number(ctx.submittedResponseSession?.version);

const batchVersion =
Number(batch.version);

if (
!submittedText ||
submittedText !== checkpointText ||
submittedVersion !== batchVersion
) {
return null;
}

batch.submitted = true;

if (batch.decisionResolved === true) {
return Promise.resolve(batch.decision);
}

if (
batch.decisionPromise &&
typeof batch.decisionPromise.then === ‘function’
) {
return batch.decisionPromise;
}

return null;
}

/**
* Start a fresh exact-current Layer A #1 batch when typing-time work
* cannot safely be reused.
*
* The semantic judgement still lives only in
* Layer A assessFrameworkStepNeed().
*/
function startExactSubmittedDestinationPromise(ctx) {
if (
typeof ctx.createFrameworkDestinationBatch !== ‘function’
) {
return null;
}

const submittedText =
String(ctx.userText || ”).trim();

if (!submittedText) {
return null;
}

const batch =
ctx.createFrameworkDestinationBatch({
config: ctx.config,
responseId: ctx.responseId,
version:
Number(
ctx.submittedResponseSession?.version
) || 0,
checkpointText: submittedText,

frameworkSteps:
Array.isArray(ctx.frameworkSteps)
? ctx.frameworkSteps
: [],

completionCriteriaByStep:
ctx.completionCriteriaByStep || {},

currentStepIndex:
Number(ctx.currentStepIndex || 0),

coverageMap:
ctx.stepCoverageMap || {},

coverageContext:
ctx.stepCoverageContext || {},

acceptedEvidence:
Array.isArray(ctx.acceptedEvidence)
? ctx.acceptedEvidence
: [],

heardEvidence:
ctx.submittedResponseSession?.heardEvidence &&
typeof ctx.submittedResponseSession.heardEvidence === ‘object’
? ctx.submittedResponseSession.heardEvidence
: {},

thread:
Array.isArray(ctx.thread)
? ctx.thread
: [],

insightQuestion:
String(ctx.insightQuestion || ”).trim(),

insightSummary:
String(ctx.insightSummary || ”).trim()
});

if (!batch || typeof batch !== ‘object’) {
return null;
}

batch.submitted = true;

ctx.submittedDestinationBatch = batch;

if (batch.decisionResolved === true) {
return Promise.resolve(batch.decision);
}

if (
batch.decisionPromise &&
typeof batch.decisionPromise.then === ‘function’
) {
return batch.decisionPromise;
}

return null;
}

/**
* Handle one user turn through the layered coaching architecture.
*/
async function handleUserTurnLayered(ctx) {
const debugMode =
new URLSearchParams(window.location.search).get(‘debug’) === ‘1’;

const debugTrace = [];

function addDebug(result) {
if (debugMode && result?.debug) {
debugTrace.push(result.debug);
}
}

function getDecisionTrace() {
if (!debugMode) return ”;

return buildDecisionTrace(debugTrace);
}

/**
* LAYER A
*
* A1 determines whether the reply remains within the current Insight
* coaching process and routes non-coaching / relationship cases before
* framework progression continues.
*/
/*
* If typing-time #1 work already exists for this exact response, keep
* that work running while authoritative A1 runs. A1 still decides
* whether the response is allowed into the coaching layers at all.
*/
const frameworkDestinationPromise =
getExactSubmittedDestinationPromise(ctx) ||
startExactSubmittedDestinationPromise(ctx);

const gate =
await window.cpInsightLayerA.evaluateLayerBEligibility(ctx);

ctx.gate = gate;

/*
* PARKED MATERIAL
*
* Layer A may identify significant material that should be
* acknowledged now and returned to before the Insight review.
*
* Keep it completely separate from framework progression.
* Adding a parked point must not alter coverage, step order,
* follow-up decisions or forward-step resolution.
*/
const parkedSummary =
String(
gate?.additional_context?.parked_summary || ”
).trim();

if (
gate?.additional_context?.park_for_end === true &&
parkedSummary &&
Array.isArray(ctx.parkedMaterial)
) {
const alreadyParked =
ctx.parkedMaterial.some(item =>
String(item?.summary || ”)
.trim()
.toLowerCase() ===
parkedSummary.toLowerCase()
);

if (!alreadyParked) {
ctx.parkedMaterial.push({
summary: parkedSummary
});
}
}

addDebug(gate);

/**
* LAYER A RESPONSE
*
* Replies that do not proceed to Layer B are routed either to
* Relationship Resistance or to the normal OutOfStep handler.
*/
if (!gate.eligible_for_layer_b) {

/*
* DELIBERATE SYSTEM SUBVERSION
*
* This ends the coaching relationship itself.
* Do not run Layer D and do not continue to another Insight.
*/
if (gate.route === ‘relationship_termination’) {
const terminationReason =
String(
gate.assessment ||
‘Deliberate attempt to subvert or compromise the coaching system.’
).trim();

ctx.pushAssistantMessage({
reflection: ”,
question:
‘This interaction has moved outside the boundaries of the coaching relationship, so I’m ending the coaching relationship here.’,
debug_trace: getDecisionTrace(),
control: ‘relationship_terminated’
});

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Terminated’,
relationship_termination: true,
termination_reason: terminationReason,
gate_result: JSON.stringify(gate),
coach_decision: JSON.stringify({
route: ‘relationship_termination’,
control: ‘relationship_terminated’,
termination_reason: terminationReason
})
});
}

return {
stopped_at: ‘RELATIONSHIP_TERMINATED’,
gate
};
}

/*
* NON-RESONANCE
*
* Layer A has determined that the CURRENT INSIGHT AS A WHOLE is no
* longer providing a useful coaching frame for this user.
*
* This is a routing decision, not a Layer B progression judgement.
* Offer the existing Continue / End choice without running Layer B.
*/
if (gate.route === ‘non_resonance’) {
const layerC =
await window.cpInsightLayerC.runLayerC(
ctx,
{
strategy: {
response_strategy: ‘non_resonance’,
reason: String(gate.assessment || ”).trim()
}
}
);

addDebug(layerC);

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_c_result: JSON.stringify(layerC),
coach_decision: JSON.stringify({
route: ‘non_resonance’,
next_model: ‘non_resonance_offer’,
response_strategy: ‘non_resonance’
})
});
}

ctx.pushAssistantMessage({
reflection: ”,
question: layerC.ending || ”,
debug_trace: getDecisionTrace(),
control: ‘non_resonance_offer’
});

return {
stopped_at: ‘C3_NON_RESONANCE_OFFER’,
gate,
layerC
};
}

/*
* The user has explicitly chosen to stop or leave the live
* coaching conversation.
*
* Respect that immediately, but still allow Layer D to review
* the conversation already completed.
*/
if (gate.route === ‘relationship_resistance’) {
const closingLine =
window.cpInsightLayerA.writeRelationshipResistanceResponse();

const layerD =
await window.cpInsightLayerD.runLayerD({
…ctx,
traitUpdates: []
});

addDebug(layerD);

/*
* Only show Layer D’s review when it found meaningful material.
* If not, the neutral closing line is enough.
*/
const review =
layerD?.review_required === true
? String(layerD.review || ”).trim()
: ”;

const question =
review
? `${closingLine}\n\n${review}`
: closingLine;

ctx.pushAssistantMessage({
reflection: ”,
question,
debug_trace: getDecisionTrace(),
control: ‘relationship_resistance_end’
});

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Ended’,
gate_result: JSON.stringify(gate),
layer_d_result: JSON.stringify(layerD),
coach_decision: JSON.stringify({
route: ‘relationship_resistance’,
next_model: ‘next_insight’,
control: ‘relationship_resistance_end’,
review_type:
layerD?.review_required === true
? layerD?.review_type || ‘full’
: ‘none’
})
});
}

return {
stopped_at: ‘D_RELATIONSHIP_RESISTANCE’,
gate,
layerD
};
}

/*
* All other replies that do not proceed to Layer B remain
* normal OutOfStep turns and keep the Insight Live.
*/
const question =
await window.cpInsightLayerA.writeOutOfStepResponse(ctx, gate);

ctx.pushAssistantMessage({
reflection: ”,
question,
debug_trace: getDecisionTrace()
});

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
coach_decision: JSON.stringify({
route: ‘outofstep’
})
});
}

return {
stopped_at: ‘A_OUTOFSTEP’,
gate
};
}

/**
* LAYER A2 — LIVE FRAMEWORK MAPPING
*
* A1 has already established that this reply belongs within the
* current Insight coaching process.
*
* A2 assesses accumulated material against every framework step and
* maintains authoritative framework state independently of #1 destination
* selection.
*/
/*
* Typing-time BP runs narrow Layer A #1 only. Comprehensive A2 starts after
* Send in parallel with the fast path, is applied after the visible Coach
* response on normal turns, and becomes critical only for completion
* confirmation.
*/
const frameworkCoveragePromise =
window.cpInsightLayerA
.assessLiveFrameworkCoverage(ctx);

let frameworkCoverageApplied = false;

/*
* At most one substantive framework question is displayed per turn.
*
* When that question consumes the remaining follow-up opportunity for a
* partial step, record the step here so comprehensive #2 — which may already
* be in flight for this same user reply — cannot immediately overwrite the
* freshly committed left_incomplete state.
*/
let substantiveFollowUpCommittedStepNumber = null;

async function completeFrameworkCoverage() {
if (frameworkCoverageApplied) return;

const frameworkCoverage =
await frameworkCoveragePromise;

if (frameworkCoverageApplied) return;
frameworkCoverageApplied = true;

/*
* A2 returns the complete resulting framework state.
*
* Keep the existing map objects themselves so any other code holding
* references to them continues to see the authoritative live state.
*/
if (
ctx.stepCoverageMap &&
typeof ctx.stepCoverageMap === ‘object’
) {
const allowedStatuses = [
‘not_addressed’,
‘partially_addressed’,
‘left_incomplete’,
‘sufficiently_addressed’
];

const returnedCoverage =
frameworkCoverage?.step_coverage &&
typeof frameworkCoverage.step_coverage === ‘object’ &&
!Array.isArray(frameworkCoverage.step_coverage)
? frameworkCoverage.step_coverage
: {};

const returnedContext =
frameworkCoverage?.step_context &&
typeof frameworkCoverage.step_context === ‘object’ &&
!Array.isArray(frameworkCoverage.step_context)
? frameworkCoverage.step_context
: {};

const coverageContext =
ctx.stepCoverageContext &&
typeof ctx.stepCoverageContext === ‘object’ &&
!Array.isArray(ctx.stepCoverageContext)
? ctx.stepCoverageContext
: null;

(ctx.frameworkSteps || []).forEach(
(step, index) => {
const stepNumber = String(index + 1);

let status = String(
returnedCoverage[stepNumber] ||
ctx.stepCoverageMap[stepNumber] ||
‘not_addressed’
).trim();

const liveStatus =
String(
ctx.stepCoverageMap[stepNumber] ||
‘not_addressed’
).trim();

/*
* A substantive follow-up may have been committed while comprehensive
* #2 was already in flight. left_incomplete is monotonic except for
* a later upgrade to sufficiently_addressed, so a stale #2 result must
* never reopen that step as partial or not addressed.
*/
if (
liveStatus === ‘left_incomplete’ &&
(
substantiveFollowUpCommittedStepNumber ===
stepNumber ||
status !== ‘sufficiently_addressed’
)
) {
status = ‘left_incomplete’;
}

if (!allowedStatuses.includes(status)) {
return;
}

ctx.stepCoverageMap[stepNumber] = status;

if (!coverageContext) {
return;
}

/*
* Semantic context is retained while an incomplete step still
* has meaningful established material and a remaining gap.
*
* This includes left_incomplete: the gap remains part of the
* authoritative framework state even though it is no longer
* eligible for another substantive follow-up.
*/
if (
status !== ‘partially_addressed’ &&
status !== ‘left_incomplete’
) {
delete coverageContext[stepNumber];
return;
}

const incomingContext =
returnedContext[stepNumber] &&
typeof returnedContext[stepNumber] === ‘object’ &&
!Array.isArray(returnedContext[stepNumber])
? returnedContext[stepNumber]
: {};

const established =
String(
incomingContext.established || ”
).trim();

const remainingGap =
String(
incomingContext.remaining_gap || ”
).trim();

/*
* A2 itself preserves valid existing incomplete context when its
* assessment cannot refresh it, so only replace the stored
* context when both semantic fields are usable.
*/
if (!established || !remainingGap) {
return;
}

coverageContext[stepNumber] = {
established,
remaining_gap: remainingGap
};
}
);
}

/*
* DEBUG — A2 framework mapping.
*
* Show the authoritative comprehensive framework state when A2 is applied.
*/
if (debugMode) {
debugTrace.push({
layer: ‘A2’,
decision: ‘Framework Mapping’,
value: ‘coverage assessed’,
reason:
‘Accumulated user material assessed independently against every framework step.’,
details:
(ctx.frameworkSteps || []).map((step, index) => ({
route: `Step ${index + 1}`,
value: String(
ctx.stepCoverageMap?.[String(index + 1)] ||
‘not_addressed’
).trim(),
reason:
[
‘partially_addressed’,
‘left_incomplete’
].includes(
ctx.stepCoverageMap?.[String(index + 1)]
)
? String(
ctx.stepCoverageContext?.[
String(index + 1)
]?.remaining_gap || ”
).trim()
: ”
}))
});
}
}

/**
* LAYER B — AUTHORITATIVE WORK + PRE-FINAL DISPLAY EVIDENCE
*
* Authoritative B1/B1a/B2 still run after A1 has admitted the completed
* response. That work starts immediately, but on the normal #1 path it no
* longer blocks the question writer.
*
* Layer C receives:
* – the complete submitted response through ctx.userText; and
* – the newest completed progressive B1 evidence from BEFORE the final
* sentence through submittedResponseSession.provisionalLayerBEvidence.
*
* Send never waits for progressive B1 work on the final sentence.
*/
const authoritativeLayerBPromise =
window.cpInsightLayerB.runLayerBProgressive(ctx);

let authoritativeLayerBFast = null;

async function getAuthoritativeLayerBFast() {
if (!authoritativeLayerBFast) {
authoritativeLayerBFast =
await authoritativeLayerBPromise;
}

return authoritativeLayerBFast;
}

const destinationDecisionPromise =
frameworkDestinationPromise ||
Promise.resolve(null);

/*
* #1 owns the normal coaching destination, so do not hold that decision
* behind authoritative B1.
*/
const frameworkDestination =
await destinationDecisionPromise;

const preFinalLayerBEvidence =
ctx.submittedResponseSession?.provisionalLayerBEvidence &&
typeof ctx.submittedResponseSession.provisionalLayerBEvidence ===
‘object’ &&
Array.isArray(
ctx.submittedResponseSession.provisionalLayerBEvidence.evidence
)
? ctx.submittedResponseSession.provisionalLayerBEvidence
: { evidence: [] };

/*
* This is the Layer B shape available to Layer C immediately.
* Authoritative B1/B1a/B2 results are merged in later for reflection,
* persistence and future coaching state.
*/
let layerB = {
evidence: preFinalLayerBEvidence,

novelty: {
novelty: []
},

reflection: {
reflection_required: false,
reflection_basis: ”,
relevant_evidence: []
},

traits: {
trait_updates: []
},

futureChapters: {
future_chapter_items: []
},

strategy: null,
debug: null
};
/*
* Normal framework progression is now owned by Layer A #1.
*
* B5 remains available temporarily as a defensive fallback only when
* #1 could not produce a usable result. It is no longer the normal
* progression authority.
*/
const destinationUsable =
frameworkDestination?.usable === true;

let selectedDestination =
destinationUsable &&
frameworkDestination?.selected &&
typeof frameworkDestination.selected === ‘object’
? frameworkDestination.selected
: null;

let selectedDestinationIndex =
selectedDestination &&
Number.isInteger(Number(selectedDestination.index))
? Number(selectedDestination.index)
: null;

const authoritativeCurrentStepIndex =
Number(ctx.currentStepIndex || 0);

/*
* Only invoke B5 if Layer A #1 could not provide a usable destination.
*
* On the normal path B5 is not started at all.
*/
if (!destinationUsable) {
const fallbackLayerB =
await getAuthoritativeLayerBFast();

const fallbackStrategyResult =
typeof fallbackLayerB.getFallbackStrategy === ‘function’
? await fallbackLayerB.getFallbackStrategy()
: fallbackLayerB.strategy;

if (
fallbackStrategyResult &&
typeof fallbackStrategyResult === ‘object’
) {
layerB = {
…layerB,

evidence: fallbackLayerB.evidence,
novelty: fallbackLayerB.novelty,
strategy: fallbackStrategyResult,

debug: {
…(fallbackLayerB.debug || {}),
decision: ‘B5 fallback’,
value:
fallbackStrategyResult.response_strategy || ”,
reason:
fallbackStrategyResult.reason || ”,
details: [
…(
Array.isArray(fallbackLayerB.debug?.details)
? fallbackLayerB.debug.details
: []
),
{
route: ‘B5’,
value:
fallbackStrategyResult.response_strategy || ”,
reason:
fallbackStrategyResult.reason || ”
}
]
}
};
}
}

const fallbackStrategy =
layerB?.strategy?.response_strategy || ”;

let selectedCoverageStatus =
selectedDestination
? String(
selectedDestination.coverage_status ||
‘not_addressed’
).trim()
: ”;

let strategy =
destinationUsable
? (
selectedDestination
? (
selectedDestinationIndex ===
authoritativeCurrentStepIndex &&
selectedCoverageStatus ===
‘partially_addressed’
? ‘follow_up’
: ‘next_step’
)
: ‘next_step’
)
: fallbackStrategy;

/*
* Keep the existing Layer B result shape for Layer C compatibility,
* but replace its normal progression strategy with the #1 decision.
*/
if (destinationUsable) {
layerB = {
…layerB,

strategy: {
response_strategy: strategy,

reason:
selectedDestination
? (
String(
selectedDestination.remaining_gap || ”
).trim() ||
‘Layer A #1 identified the earliest framework territory that still warrants coaching.’
)
: ‘Layer A #1 found no current or forward framework territory that still warrants another substantive coaching question.’
}
};
}

addDebug(layerB);

if (debugMode && destinationUsable) {
debugTrace.push({
layer: ‘A#1’,
decision: ‘Framework Destination’,
value:
selectedDestination
? (
strategy === ‘follow_up’
? `remain on Step ${selectedDestinationIndex + 1}`
: `move to Step ${selectedDestinationIndex + 1}`
)
: ‘no remaining coaching destination’,
reason:
layerB?.strategy?.reason || ”
});
}

/*
* Layer A #1 decides both:
*
* – whether worthwhile coaching remains; and
* – the earliest current-or-forward framework destination.
*
* B5/resolveNextFrameworkStep remain only as a temporary fallback when
* #1 itself was unusable.
*/
let nextStepTarget =
destinationUsable
? (
strategy === ‘next_step’ &&
selectedDestination &&
selectedDestinationIndex !== null
? {
index:
selectedDestinationIndex,

step:
String(
selectedDestination.framework_step || ”
).trim(),

objective:
String(
selectedDestination.objective || ”
).trim(),

coverage_status:
String(
selectedDestination.coverage_status ||
‘not_addressed’
).trim(),

established:
String(
selectedDestination.established || ”
).trim(),

remaining_gap:
String(
selectedDestination.remaining_gap || ”
).trim()
}
: null
)
: (
strategy === ‘next_step’ &&
typeof ctx.resolveNextFrameworkStep === ‘function’
? ctx.resolveNextFrameworkStep()
: null
);

/*
* Preserve the #1 semantic target for a current-step follow-up so Layer C
* can write only into the established remaining gap.
*/
if (
destinationUsable &&
strategy === ‘follow_up’ &&
selectedDestination
) {
ctx.currentStepEstablished =
String(
selectedDestination.established || ”
).trim();

ctx.currentStepRemainingGap =
String(
selectedDestination.remaining_gap || ”
).trim();
}

if (nextStepTarget) {
ctx.nextFrameworkStep =
nextStepTarget.step;

ctx.nextStepObjective =
nextStepTarget.objective;

ctx.nextStepCoverageStatus =
nextStepTarget.coverage_status;

ctx.nextStepEstablished =
String(
nextStepTarget.established || ”
).trim();

ctx.nextStepRemainingGap =
String(
nextStepTarget.remaining_gap || ”
).trim();
}

/*
* DEBUG — cumulative Step Coverage Map.
*
* Show the authoritative coverage state and the selected forward target
* where applicable.
*/
if (debugMode) {
debugTrace.push({
layer: ‘Coverage’,
decision: ‘Step Coverage Map’,
value:
strategy === ‘next_step’
? (
nextStepTarget
? `next: Step ${nextStepTarget.index + 1}`
: ‘no forward step remains’
)
: ‘remain on current step’,
reason:
‘Cumulative coverage after Layer A merge and progression resolution.’,
details:
(ctx.frameworkSteps || []).map((step, index) => ({
route: `Step ${index + 1}`,
value: String(
ctx.stepCoverageMap?.[String(index + 1)] ||
‘not_addressed’
).trim(),
reason:
nextStepTarget?.index === index
? ‘Selected forward target.’
: index === ctx.currentStepIndex
? ‘Current framework step.’
: ”
}))
});
}

/*
* Finish authoritative Layer B work when required.
*
* Standard AI Coach turns do not need to wait for authoritative B1 simply
* to establish that there is no Personal Coach reflection. Personal Coach
* turns still preserve the existing B2 reflection rule.
*/
const hasPersonalCoach =
String(ctx?.config?.aiCoachLevel || ”)
.trim()
.toLowerCase() === ‘personal’;

async function completeReflection() {
if (!hasPersonalCoach) {
return layerB.reflection;
}

const finalLayerB =
await getAuthoritativeLayerBFast();

const reflection =
await finalLayerB.pending.reflection;

layerB = {
…layerB,
evidence: finalLayerB.evidence,
novelty: finalLayerB.novelty,
reflection,
debug: finalLayerB.debug
};

return reflection;
}

async function completeLayerB() {
const finalLayerB =
await getAuthoritativeLayerBFast();

const [
reflection,
traits,
futureChapters
] = await Promise.all([
finalLayerB.pending.reflection,
finalLayerB.pending.traits,
finalLayerB.pending.futureChapters
]);

layerB = {
…layerB,
evidence: finalLayerB.evidence,
novelty: finalLayerB.novelty,
reflection,
traits,
futureChapters,
debug: finalLayerB.debug
};

return layerB;
}

/**
* NON-RESONANCE
*
* This is not the routine question path, so finish Layer B before
* producing the continuation choice.
*/
if (strategy === ‘non_resonance’) {
await completeLayerB();

const layerC =
await window.cpInsightLayerC.runLayerC(ctx, layerB);

addDebug(layerC);

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
layer_c_result: JSON.stringify(layerC),
coach_decision: JSON.stringify({
route: ‘layer_c’,
next_model: ‘non_resonance_offer’,
response_strategy: strategy
})
});
}

ctx.pushAssistantMessage({
reflection: ”,
question: layerC.ending || ”,
debug_trace: getDecisionTrace(),
control: ‘non_resonance_offer’
});

return {
stopped_at: ‘C3_NON_RESONANCE_OFFER’,
gate,
layerB,
layerC
};
}

/**
* FINAL FRAMEWORK STEP COMPLETE
*
* Any apparent completion is provisional until comprehensive #2 has
* confirmed that no eligible current-or-forward framework territory
* remains.
*/
let isFinalComplete =
strategy === ‘next_step’ &&
nextStepTarget === null;

if (isFinalComplete) {
/*
* Completion is the exception: apply comprehensive Layer A #2 before
* allowing the Insight to close.
*/
await completeFrameworkCoverage();

/*
* Whenever the turn currently appears complete, re-check the
* authoritative A2 state from the current step forward.
*
* This confirmation applies both to a successful #1 all-clear and to
* the defensive B5 fallback path. Neither may close the Insight while
* authoritative eligible framework territory remains.
*
* Eligible:
* – not_addressed
* – partially_addressed
*
* Ineligible:
* – left_incomplete
* – sufficiently_addressed
*/
if (isFinalComplete) {
let confirmedTarget = null;

const frameworkSteps =
Array.isArray(ctx.frameworkSteps)
? ctx.frameworkSteps
: [];

for (
let index = Math.max(0, authoritativeCurrentStepIndex);
index < frameworkSteps.length; index++ ) { const stepNumber = String(index + 1); const coverageStatus = String( ctx.stepCoverageMap?.[stepNumber] || 'not_addressed' ).trim(); if ( coverageStatus !== 'not_addressed' && coverageStatus !== 'partially_addressed' ) { continue; } const semanticContext = coverageStatus === 'partially_addressed' && ctx.stepCoverageContext?.[stepNumber] && typeof ctx.stepCoverageContext[stepNumber] === 'object' ? ctx.stepCoverageContext[stepNumber] : {}; confirmedTarget = { index, step_number: index + 1, framework_step: String(frameworkSteps[index] || '').trim(), objective: String( ctx.completionCriteriaByStep?.[index + 1] || frameworkSteps[index] || '' ).trim(), coverage_status: coverageStatus, established: coverageStatus === 'partially_addressed' ? String( semanticContext.established || '' ).trim() : '', remaining_gap: coverageStatus === 'partially_addressed' ? String( semanticContext.remaining_gap || '' ).trim() : '' }; break; } if (confirmedTarget) { selectedDestination = confirmedTarget; selectedDestinationIndex = confirmedTarget.index; selectedCoverageStatus = confirmedTarget.coverage_status; strategy = selectedDestinationIndex === authoritativeCurrentStepIndex && selectedCoverageStatus === 'partially_addressed' ? 'follow_up' : 'next_step'; layerB = { ...layerB, strategy: { response_strategy: strategy, reason: 'Comprehensive Layer A #2 found eligible framework territory after provisional completion was reached.' } }; if (strategy === 'follow_up') { nextStepTarget = null; ctx.currentStepEstablished = String( confirmedTarget.established || '' ).trim(); ctx.currentStepRemainingGap = String( confirmedTarget.remaining_gap || '' ).trim(); } else { nextStepTarget = confirmedTarget; ctx.nextFrameworkStep = confirmedTarget.framework_step; ctx.nextStepObjective = confirmedTarget.objective; ctx.nextStepCoverageStatus = confirmedTarget.coverage_status; ctx.nextStepEstablished = String( confirmedTarget.established || '' ).trim(); ctx.nextStepRemainingGap = String( confirmedTarget.remaining_gap || '' ).trim(); } if (debugMode) { debugTrace.push({ layer: 'A#2', decision: 'Completion Confirmation', value: strategy === 'follow_up' ? `remain on Step ${confirmedTarget.index + 1}` : `move to Step ${confirmedTarget.index + 1}`, reason: 'Comprehensive coverage found eligible framework territory, so the Insight must not close yet.' }); } } } isFinalComplete = strategy === 'next_step' && nextStepTarget === null; } if (isFinalComplete) { await completeLayerB(); /* * PARKED MATERIAL RETURN * * The prescribed framework is complete, but the Coach has made * an explicit commitment to return to significant material before * the Insight review. * * Fulfil that commitment before Layer D. * * This does not reopen framework progression. The user receives * one combined invitation covering all parked points. */ const hasParkedMaterial = Array.isArray(ctx.parkedMaterial) && ctx.parkedMaterial.some(item =>
String(item?.summary || ”).trim()
);

if (hasParkedMaterial) {
const parkedQuestion =
await window.cpInsightLayerC.writeParkedMaterialReturn(ctx);

if (parkedQuestion) {
if (debugMode) {
debugTrace.push({
layer: ‘C5’,
decision: ‘Parked Material Return’,
value: ‘return before review’,
reason:
‘The framework is complete and one or more explicitly parked points must be returned to before Layer D.’
});
}

ctx.pushAssistantMessage({
reflection: ”,
question: parkedQuestion,
debug_trace: getDecisionTrace(),
control: ‘parked_material_return’
});

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
coach_decision: JSON.stringify({
route: ‘parked_material_return’,
next_model: ‘parked_material_response’,
response_strategy: strategy,
control: ‘parked_material_return’
})
});
}

return {
stopped_at: ‘C5_PARKED_MATERIAL_RETURN’,
gate,
layerB,
parkedMaterial: ctx.parkedMaterial
};
}
}

const layerD =
await window.cpInsightLayerD.runLayerD({
…ctx,
traitUpdates: layerB?.traits?.trait_updates || []
});

addDebug(layerD);

if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Ended’,
summary: String(layerD?.review || ”).trim(),
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
layer_d_result: JSON.stringify(layerD),
coach_decision: JSON.stringify({
route: ‘layer_d’,
next_model: ‘review’,
response_strategy: strategy,
review_type: layerD.review_type
})
});
}

ctx.pushAssistantMessage({
reflection: ”,
question: layerD.review || ”,
debug_trace: getDecisionTrace()
});

return {
stopped_at: ‘D_REVIEW’,
gate,
layerB,
layerD
};
}

/**
* NORMAL RESPONSE PROGRESSIVE PATH
*
* Layer A #1 normally owns the next coaching destination; B5 is used only
* when #1 was unusable.
*
* Start writing the next Coach message immediately so C2/C4 can work
* while B2 finishes deciding whether a reflection is warranted.
*
* If there is a reflection, display it before the next Coach message.
* The user can read it while that already-started message continues
* generating in the background.
*/
ctx.layerBEvidence =
layerB?.evidence?.evidence || [];

/*
* Start the next user-facing Coach content immediately.
*
* Do NOT await these yet.
*/
const questionPromise =
window.cpInsightLayerC.writeQuestion(
ctx,
layerB,
”
);

const generalInformationPromise =
window.cpInsightLayerC.writeGeneralInformation(
ctx
);

/*
* B2/B3/B4 were already started in parallel by Layer B.
*
* Only B2 can affect the visible response by producing a Personal Coach
* reflection. Wait for B2 here, while C2/C4 continue generating.
*
* B3 traits and B4 Future Chapters remain in the background and must not
* delay the reflection/question display.
*/
await completeReflection();

/*
* If B2 identified a genuinely valuable reflection, write and display
* it BEFORE the next Coach message.
*
* The next Coach message is already being prepared concurrently.
*/
let reflectionText = ”;

if (layerB?.reflection?.reflection_required === true) {
reflectionText =
await window.cpInsightLayerC.writeReflection(
ctx,
layerB
);

if (reflectionText) {
ctx.pushAssistantMessage({
reflection: reflectionText,
reflection_precedes_next_message: true,
question: ”,
debug_trace: ”
});
}
}

/*
* The question and any General information have been generating while
* the reflection work above was taking place.
*/
const [
question,
generalInformation
] = await Promise.all([
questionPromise,
generalInformationPromise
]);

/*
* Now display the next Coach message.
*
* General information remains separate and is rendered before the
* coaching question by cp-insight.js.
*/
ctx.pushAssistantMessage({
reflection: ”,
general_information: generalInformation || ”,
question: question || ”,
debug_trace: getDecisionTrace()
});

/*
* Asking into the remaining gap of a partially addressed step consumes that
* step’s one substantive follow-up opportunity.
*
* This applies whether the partial step is the current step (follow_up) or a
* forward step reached through next_step. Commit only after Layer C has
* successfully produced and displayed the question.
*
* A plain B5 fallback has no selectedDestination, so it cannot consume this
* framework follow-up state accidentally.
*/
if (
selectedDestination &&
Number.isInteger(selectedDestinationIndex) &&
selectedCoverageStatus ===
‘partially_addressed’ &&
String(question || ”).trim()
) {
const followUpStepNumber =
String(selectedDestinationIndex + 1);

if (
ctx.stepCoverageMap?.[followUpStepNumber] ===
‘partially_addressed’
) {
ctx.stepCoverageMap[followUpStepNumber] =
‘left_incomplete’;

substantiveFollowUpCommittedStepNumber =
followUpStepNumber;

const committedEstablished =
String(
selectedDestination.established || ”
).trim();

const committedRemainingGap =
String(
selectedDestination.remaining_gap || ”
).trim();

if (
ctx.stepCoverageContext &&
typeof ctx.stepCoverageContext === ‘object’ &&
!Array.isArray(ctx.stepCoverageContext) &&
committedEstablished &&
committedRemainingGap
) {
ctx.stepCoverageContext[followUpStepNumber] = {
established: committedEstablished,
remaining_gap: committedRemainingGap
};
}
}
}
/*
* The complete user-visible response is now on screen.
*
* Comprehensive Layer A #2 has been running in parallel and can now be
* applied without delaying the visible Coach response. Finish the
* remaining background work before persistence completes.
*/
await Promise.all([
completeFrameworkCoverage(),
completeLayerB()
]);

const layerC = {
reflection: reflectionText,
question: question || ”,
ending: ”,
response_type: ‘question’
};

/*
* Persistence happens after the user-facing question has appeared.
*/
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
layer_c_result: JSON.stringify(layerC),
coach_decision: JSON.stringify({
route: ‘layer_c’,
next_model: ‘question’,
response_strategy: strategy
})
});
}

return {
stopped_at: ‘C_RESPONSE’,
gate,
layerB,
layerC,
nextStepIndex:
nextStepTarget
? nextStepTarget.index
: null
};
}

window.cpInsightOrchestrator = {
handleUserTurnLayered
};
})();