Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,56 @@ test('does not apply the counterpart-omission hard fail on non-relational turns'
assert.equal(result.generated, true);
assert.equal(result.acceptedReplySource, 'llm_stream');
});

test('labels stream-stall fallbacks with Clouded Skies on solo turns', () => {
const result = resolveReplyIntegrity({
reply: '',
relationalRequested: false,
anchorName: 'DH Cross',
activeCounterpartName: null,
relationalFallback: null,
providerFinishReason: 'stream_stall',
providerCompleted: false,
});

assert.equal(result.generated, false);
assert.equal(result.isFallback, true);
assert.equal(result.droppedReplyReason, 'empty_generation');
assert.equal(result.systemNotice, 'GENERATION_INCOMPLETE');
assert.match(result.reply, /Clouded Skies/);
assert.match(result.reply, /Response incomplete/);
});

test('labels stream-stall fallbacks with Clouded Skies on relational turns', () => {
const result = resolveReplyIntegrity({
reply: '',
relationalRequested: true,
anchorName: 'DH Cross',
activeCounterpartName: 'Alex',
relationalFallback: null,
providerFinishReason: 'stream_stall',
providerCompleted: false,
});

assert.equal(result.generated, false);
assert.equal(result.isFallback, true);
assert.equal(result.droppedReplyReason, 'empty_generation');
assert.equal(result.systemNotice, 'RELATIONAL_GENERATION_INCOMPLETE');
assert.match(result.reply, /Clouded Skies/);
assert.match(result.reply, /Relationship Mapping did not complete/i);
});

test('does not label normal incomplete generations with Clouded Skies (only stalls do)', () => {
const result = resolveReplyIntegrity({
reply: '',
relationalRequested: false,
anchorName: 'DH Cross',
activeCounterpartName: null,
relationalFallback: null,
providerFinishReason: '',
providerCompleted: false,
});

assert.equal(result.generated, false);
assert.doesNotMatch(result.reply, /Clouded Skies/);
});
75 changes: 74 additions & 1 deletion vessel/src/app/api/raven-chat/__tests__/userBlockBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
phaseCapsules: [],
startedAt: new Date().toISOString(),
phaseEnteredAt: 0,
} as any,

Check warning on line 63 in vessel/src/app/api/raven-chat/__tests__/userBlockBuilder.test.ts

View workflow job for this annotation

GitHub Actions / Advisory Lint Check

Unexpected any. Specify a different type
history: [],
message: 'How bad does it look this week?',
upstreamResonance: null,
Expand All @@ -77,7 +77,7 @@
summary: 'Personal circumstance in scope: work/career; loss/disruption. Timing signal: recent.',
snippet: 'I lost my job this week.',
},
} as any);

Check warning on line 80 in vessel/src/app/api/raven-chat/__tests__/userBlockBuilder.test.ts

View workflow job for this annotation

GitHub Actions / Advisory Lint Check

Unexpected any. Specify a different type

assert.match(blocks.circumstanceContext || '', /User-described circumstances \(structured\):/);
assert.match(blocks.circumstanceContext || '', /work/);
Expand Down Expand Up @@ -160,4 +160,77 @@

assert.match(blocks.relationalAngleIntegrity || '', /do not share the same ascendant interface/i);
assert.match(blocks.relationalAngleIntegrity || '', /Treat ascendant or rising-sign language as shared only when this integrity line explicitly says they match/i);
});
});

test('creator mirror offline telemetry still allows creator-mode help after acknowledgment', () => {
const blocks = buildUserBlocks({
profile: { name: 'DH Cross' },
primaryArchitectureSummary: 'stable architecture',
sealedCognitiveArchitecture: null,
blueprintNavigationScopeActive: false,
cognitiveCalibrationScopeActive: false,
constitutionalCalibrationBlock: '',
modeContract: MODE_CONTRACTS.CHAT,
blueprintSliceContext: null,
liveLocation: null,
liveLocationQuery: null,
telemetryProfile: { name: 'DH Cross' },
hasKnownLocation: true,
stagedRosterSummary: 'none',
stagedProfileSnapshot: {
rosterNames: [],
activeCounterpartName: null,
contextOnlyNames: [],
},
stagedTurnPolicyLine: 'No staged counterpart this turn.',
stagedArchitectureSummary: 'none',
operatorIsAnchor: true,
operatorLabel: 'DH Cross',
anchorLabel: 'DH Cross',
relationalMappingRequested: false,
counterpartIsDeceased: false,
effectiveRelationalContext: null,
relationalSharedStormReport: null,
relationalSharedStormError: null,
session: {
id: 'session-1',
turnCount: 1,
resonanceHits: 0,
missCount: 0,
openThreads: [],
isSealed: false,
calibration: {
locationConfirmed: true,
birthDataConfirmed: true,
blueprintConfirmed: true,
},
phase: 'calibration',
phaseCapsules: [],
startedAt: new Date().toISOString(),
phaseEnteredAt: 0,
} as any,
history: [],
message: 'Why is Raven not replying in creator mode?',
upstreamResonance: null,
telemetryBrief: 'Telemetry brief unavailable.',
astrology: null,
creatorMirrorTelemetry: {
sensorStatus: 'offline',
runRef: null,
summary: 'No telemetry run available.',
architecturalGaps: ['sensor_array_offline'],
velocityStatus: 'offline',
recommendedRepairs: ['restore sensor runs'],
kernelSkillContext: null,
websiteSkillContext: null,
offlineReason: 'No Sherlog runs found.',
},
fieldReportVoiceContext: null,
lunarPhaseContext: null,
circumstanceDisclosure: null,
} as any);

assert.match(blocks.creatorMirrorTelemetry || '', /Acknowledge the sensor array is unavailable in one plain sentence/i);
assert.match(blocks.creatorMirrorTelemetry || '', /then continue helping in creator mode using known contracts/i);
assert.match(blocks.creatorMirrorTelemetry || '', /Do not refuse the turn solely because the sensor array is offline/i);
});
8 changes: 8 additions & 0 deletions vessel/src/app/api/raven-chat/generationIntegrity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ function hasBannedClosingQuestions(text: string): boolean {
);
}

const STREAM_STALL_FINISH_REASON = "stream_stall";
const CLOUDED_SKIES_LABEL =
"Clouded Skies — the channel went quiet before a full reading locked. The geometry is intact; only the rendering paused.";

function isStreamStall(finishReason?: string | null): boolean {
return (finishReason || "").trim().toLowerCase() === STREAM_STALL_FINISH_REASON;
}

function buildIncompleteReply(
input: ReplyIntegrityInput,
reason: string,
Expand Down
5 changes: 4 additions & 1 deletion vessel/src/app/api/raven-chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1922,6 +1922,7 @@
let accumulatedReply = '';
let providerFinishReason = '';
let providerCompleted = false;
let hasFirstChunk = false;
let providerInterrupted = false;
let providerInterruptionReason = '';
let finalReply = '';
Expand Down Expand Up @@ -1982,7 +1983,6 @@
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';

for (const line of lines) {
const trimmed = line.trim();
if (trimmed === '') continue;
Expand All @@ -1993,6 +1993,7 @@
if (trimmed.startsWith('data: ')) {
try {
const parsed = JSON.parse(trimmed.slice(6));
// OpenAI content format handling
const choice = parsed.choices?.[0];
const content = choice?.delta?.content;
const finishReason = typeof choice?.finish_reason === 'string'
Expand All @@ -2003,6 +2004,7 @@
}
if (content) {
accumulatedReply += content;
hasFirstChunk = true;

Check warning on line 2007 in vessel/src/app/api/raven-chat/route.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this useless assignment to variable "hasFirstChunk".

See more on https://sonarcloud.io/project/issues?id=DHCross_Shipyard&issues=AZ3M-ivaD7O0wlxkixUC&open=AZ3M-ivaD7O0wlxkixUC&pullRequest=455
}
} catch (err) {
// ignore JSON parse errors
Expand All @@ -2028,6 +2030,7 @@
// Ignore cancellation failures
}
}


finalReply = accumulatedReply;
currentIsRelational = relationalMappingRequested
Expand Down
8 changes: 3 additions & 5 deletions vessel/src/app/api/raven-chat/userBlockBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,9 @@ function buildCreatorMirrorTelemetryBlock(creatorMirrorTelemetry: CreatorMirrorT
: '';

if (creatorMirrorTelemetry.sensorStatus === 'offline') {
const offlineReason = compactText(
creatorMirrorTelemetry.offlineReason || 'Sherlog telemetry unavailable.',
260,
);
return `Sherlog creator-mirror telemetry: OFFLINE. Reason: ${offlineReason} Acknowledge the outage briefly, then continue in creator mode using available kernel/website context and the operator's request. Use explicit assumptions where needed and avoid pretending the telemetry is live.${kernelContextBlock}${websiteContextBlock}`;
const offlineReason = creatorMirrorTelemetry.offlineReason?.trim()
|| 'No Sherlog runs found at velocity-artifacts/sherlog-runs.';
return `Sherlog creator-mirror telemetry: OFFLINE. Reason: ${offlineReason} Acknowledge the sensor array is unavailable in one plain sentence, then continue helping in creator mode using known contracts, prompt architecture, and the user’s request. Do not claim live telemetry, and do not refuse the turn solely because the sensor array is offline.${kernelContextBlock}${websiteContextBlock}`;
}

return `Sherlog creator-mirror telemetry: ${compactText({
Expand Down
17 changes: 17 additions & 0 deletions vessel/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3456,6 +3456,14 @@ export default function App() {
>
Map Us · {profile.name}
</button>
<button
type="button"
onClick={() => handleCounterpartSymbolicMoment(profile, `split_chip_symbolic_${profile.id}`)}
className="whitespace-nowrap px-2.5 py-1 text-[11px] rounded-full font-medium transition-colors border border-emerald-500/35 bg-emerald-500/12 text-emerald-200 hover:bg-emerald-500/25"
title={`Run Symbolic Moment for ${profile.name}.`}
>
Moment · {profile.name}
</button>
</React.Fragment>
);
})}
Expand Down Expand Up @@ -3898,6 +3906,7 @@ export default function App() {
soloFocusProfileId={soloFocusProfileId}
compact
onSoloMirror={(profile) => handleCounterpartSoloMirror(profile, `desktop_rail_mirror_${profile.id}`)}
onSymbolicMoment={(profile) => handleCounterpartSymbolicMoment(profile, `desktop_rail_symbolic_${profile.id}`)}
onMapPair={handleCounterpartMapPair}
onMapObserverPair={handleMapObserverPair}
onPolyadicMap={handlePolyadicMap}
Expand Down Expand Up @@ -4353,6 +4362,10 @@ export default function App() {
{/* Profile Vault */}
<ProfileVault
onProfileSelect={setActiveProfile}
onRunSymbolicMoment={(profile) => {
setIsVaultOpen(false);
handleCounterpartSymbolicMoment(profile, `vault_profile_symbolic_${profile.id}`);
}}
onOpenBlueprint={handleOpenProfileBlueprint}
onClearSession={() => {
archiveCurrentFlightRecorderRun();
Expand Down Expand Up @@ -4888,6 +4901,10 @@ export default function App() {
closeMobileControls();
handleCounterpartSoloMirror(profile, `mobile_chip_mirror_${profile.id}`);
}}
onSymbolicMoment={(profile) => {
closeMobileControls();
handleCounterpartSymbolicMoment(profile, `mobile_chip_symbolic_${profile.id}`);
}}
onMapPair={(profile) => {
closeMobileControls();
handleCounterpartMapPair(profile);
Expand Down
40 changes: 40 additions & 0 deletions vessel/src/components/ProfileVault.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
interface ProfileVaultProps {
readonly onProfileSelect: (profile: VaultProfile | null) => void;
readonly onStageProfile: (profile: VaultProfile | null, role?: StagingRole) => void;
readonly onRunSymbolicMoment?: (profile: VaultProfile) => void;
readonly onOpenBlueprint?: (profile: VaultProfile) => void | Promise<void>;
readonly onClearSession?: () => void;
readonly activeProfile: VaultProfile | null;
Expand Down Expand Up @@ -272,6 +273,7 @@
export default function ProfileVault({
onProfileSelect,
onStageProfile,
onRunSymbolicMoment,
onOpenBlueprint,
onClearSession = () => { },
activeProfile,
Expand Down Expand Up @@ -2048,7 +2050,7 @@
</select>
</div>
)}
{sortedProfiles.map(profile => {

Check failure on line 2053 in vessel/src/components/ProfileVault.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=DHCross_Shipyard&issues=AZ3LYfrbIpdPHrNPmkDO&open=AZ3LYfrbIpdPHrNPmkDO&pullRequest=455
const selected = isSelected(profile);
const selectedRole = roleByProfileId.get(profile.id);
const latestProfileCurrentLocation = getLatestSavedLocation(profile.id);
Expand Down Expand Up @@ -2422,6 +2424,25 @@
</details>
);
})()}
<button
type="button"
disabled={!isReady}
aria-disabled={!isReady}
onClick={() => {
if (!isReady || !onRunSymbolicMoment) return;
onRunSymbolicMoment(profile);
}}
className={cn(
"w-full py-2 border rounded-lg text-[10px] font-mono uppercase tracking-wider flex justify-center items-center gap-2",
isReady
? "bg-emerald-500/12 hover:bg-emerald-500/20 border-emerald-500/25 text-emerald-300"
: "bg-white/[0.02] border-white/10 text-slate-500 cursor-not-allowed"
)}
title={isReady ? `Read Symbolic Moment for ${profile.name}` : 'Complete profile fields to run Symbolic Moment'}
aria-label={isReady ? `Read Symbolic Moment for ${profile.name}` : 'Complete profile fields to run Symbolic Moment'}
>
<Clock className="w-3.5 h-3.5" /> Read Symbolic Moment
</button>
<button
type="button"
disabled={!isReady}
Expand Down Expand Up @@ -2480,6 +2501,25 @@
{stageBlockReason}
</p>
)}
<button
type="button"
disabled={!isReady}
aria-disabled={!isReady}
onClick={() => {
if (!isReady || !onRunSymbolicMoment) return;
onRunSymbolicMoment(profile);
}}
className={cn(
"mt-2 w-full py-2 border rounded-lg text-[10px] font-mono uppercase tracking-wider flex justify-center items-center gap-2",
isReady
? "bg-emerald-500/12 hover:bg-emerald-500/20 border-emerald-500/25 text-emerald-300"
: "bg-white/[0.02] border-white/10 text-slate-500 cursor-not-allowed"
)}
title={isReady ? `Read Symbolic Moment for ${profile.name}` : 'Complete profile fields to run Symbolic Moment'}
aria-label={isReady ? `Read Symbolic Moment for ${profile.name}` : 'Complete profile fields to run Symbolic Moment'}
>
<Clock className="w-3.5 h-3.5" /> Read Symbolic Moment
</button>
<button
type="button"
disabled={!isReady}
Expand Down
13 changes: 13 additions & 0 deletions vessel/src/components/chat/CounterpartQuickActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface CounterpartQuickActionsProps {
soloFocusProfileId: string | null;
compact?: boolean;
onSoloMirror: (profile: VaultProfile) => void;
onSymbolicMoment: (profile: VaultProfile) => void;
onMapPair: (profile: VaultProfile) => void;
onPolyadicMap?: () => void;
onMapObserverPair?: () => void;
Expand All @@ -20,6 +21,7 @@ export function CounterpartQuickActions({
soloFocusProfileId,
compact = false,
onSoloMirror,
onSymbolicMoment,
onMapPair,
onMapObserverPair,
onPolyadicMap,
Expand Down Expand Up @@ -58,6 +60,17 @@ export function CounterpartQuickActions({
>
Map With {profile.name}
</button>
<button
type="button"
onClick={() => onSymbolicMoment(profile)}
className={cn(
"rounded-md border-2 border-emerald-500/65 bg-emerald-500/10 px-2 py-1 text-[10px] font-mono uppercase tracking-wider text-emerald-100 transition-colors hover:border-emerald-300/85 hover:bg-emerald-500/20",
compact && "flex-1",
)}
title={`Read the current symbolic moment for ${profile.name}.`}
>
Moment: {profile.name}
</button>
</div>
);
})}
Expand Down
Loading
Loading