/**
* 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
};
})();