Skip to content
Open
Show file tree
Hide file tree
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
150 changes: 0 additions & 150 deletions LifeOS/install/hooks/lib/isa-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,156 +1249,6 @@ export function upsertSession(sessionUUID: string, sessionName: string, task: st
/** @deprecated Use upsertSession instead */
export const upsertNativeSession = upsertSession;

/**
* Mark a session as algorithm-starting in work.json. Called by
* TheRouter.hook.ts the instant the classifier emits MODE=ALGORITHM, so
* the Pulse dashboard shows the session as an algorithm session BEFORE the
* model receives the prompt — no "phase: native" wrong-display window.
*
* Behavior:
* - If a row exists for this UUID:
* - currentMode='algorithm' AND not phase='complete' → no-op (idempotent)
* - otherwise: upgrade currentMode→'algorithm', mode→'starting',
* and phase→'starting' (only when phase was 'native' — never stomps
* a real algorithm phase like 'observe' from a resumed session)
* - If no row exists: create a fresh `${datePrefix}_starting-${prefix}` slug
* with currentMode='algorithm', mode='starting', phase='starting'
*
* Side-effect: writes work.json atomically via writeRegistry.
* Best-effort: failures must not break the TheRouter classification path.
*/
/**
* Authoritatively record a session switching BACK to native (algorithm→native),
* updating `currentMode` + pushing a `modeHistory` transition so the Pulse
* Agents/Lattice dashboard re-lanes the session to the native view and the
* ModeTimeline shows the switch. Called by TheRouter (the authoritative
* classifier) on NATIVE turns.
*
* This is the DOWNGRADE path that `upsertSession` deliberately refuses: that
* guard exists to stop PromptProcessing's WEAK 8-verb regex from clobbering the
* classifier's decision a tick later. TheRouter's classifier is authoritative,
* so it IS allowed to record the return to native. `currentMode` is what every
* dashboard `inferMode`/`resolveMode` reads FIRST, so this alone re-categorizes
* the session without touching `phase`/`mode` — safe to resume the algorithm later
* (markAlgorithmStarting re-upgrades). Idempotent; failure-silent.
*/
export function markSessionNative(sessionUUID: string): void {
if (!sessionUUID) return;
try {
const registry = readRegistry();
// Deterministic targeting: the MOST-RECENT non-complete row for this uuid.
// Usually there is exactly one (upsertSession dedupes per uuid); on the rare
// "finished-but-unmarked ISA + fresh row" edge, most-recent picks the live one
// so we never flicker the wrong dashboard lane. No matching row → no-op (never
// create a row for a pure-native session that never entered work tracking).
let targetSlug: string | null = null;
let bestT = -1;
for (const [slug, session] of Object.entries(registry.sessions) as [string, any][]) {
if (session.sessionUUID !== sessionUUID) continue;
if (session.phase === 'complete') continue;
const t = new Date(session.updatedAt || session.started || 0).getTime();
if (t >= bestT) { bestT = t; targetSlug = slug; }
}
if (!targetSlug) return;
const session = registry.sessions[targetSlug];
if (session.currentMode === 'native') return; // idempotent — already native
const modeHistory: ModeTransition[] = session.modeHistory || [];
const last = modeHistory.length ? modeHistory[modeHistory.length - 1] : null;
if (last && !last.endedAt) last.endedAt = Date.now();
modeHistory.push({ mode: 'native', startedAt: Date.now() });
// Cap growth on a long oscillating session — the timeline only needs recent history.
session.modeHistory = modeHistory.length > 50 ? modeHistory.slice(-50) : modeHistory;
session.currentMode = 'native';
session.updatedAt = new Date().toISOString();
writeRegistry(registry);
} catch { /* silent — dashboard mode is best-effort */ }
}

export function markAlgorithmStarting(sessionUUID: string, taskHint: string, tier?: number): void {
if (!sessionUUID) return;
// Resolved tier ("E1".."E5") persisted onto the row so the Pulse Agents/Lattice
// page shows the correct tier the instant TheRouter classifies — before any
// ISA exists. Undefined tier leaves the existing effort untouched.
const effortStr = (typeof tier === 'number' && tier >= 1 && tier <= 5) ? `E${tier}` : undefined;
try {
const registry = readRegistry();
const timestamp = new Date().toISOString();

// Look for any non-complete row owning this UUID.
let targetSlug: string | null = null;
for (const [slug, session] of Object.entries(registry.sessions) as [string, any][]) {
if (session.sessionUUID !== sessionUUID) continue;
if (session.phase === 'complete') continue;
targetSlug = slug;
break;
}

if (targetSlug) {
const session = registry.sessions[targetSlug];
const alreadyAlgorithm = session.currentMode === 'algorithm';
const phaseIsNative = (session.phase || '').toLowerCase() === 'native';

// Idempotent: already algorithm AND not native-placeholder → just bump.
if (alreadyAlgorithm && !phaseIsNative) {
if (effortStr) session.effort = effortStr;
session.updatedAt = timestamp;
writeRegistry(registry);
return;
}

// Upgrade in place — preserve sessionUUID and slug; only flip mode/phase.
const modeHistory: ModeTransition[] = session.modeHistory || [];
if (modeHistory.length > 0) {
const last = modeHistory[modeHistory.length - 1];
if (!last.endedAt && last.mode !== 'algorithm') last.endedAt = Date.now();
}
if (!alreadyAlgorithm) {
modeHistory.push({ mode: 'algorithm', startedAt: Date.now() });
session.modeHistory = modeHistory;
}
session.currentMode = 'algorithm';
if (effortStr) session.effort = effortStr;
// Only flip the surface phase when it was the native placeholder.
// Real algorithm phases (observe/think/plan/build/execute/verify/learn/complete)
// are owned by ISASync / AlgoPhase and must not be stomped here.
if (phaseIsNative) {
session.phase = 'starting';
session.mode = 'starting';
}
session.updatedAt = timestamp;
writeRegistry(registry);
return;
}

// No row exists yet — create a fresh starting row.
const now = new Date();
const pad = (n: number) => n.toString().padStart(2, '0');
const datePrefix = `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}-${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`;
const taskSlug = (taskHint || 'starting')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
.slice(0, 40) || 'starting';
const slug = `${datePrefix}_${taskSlug}`;

registry.sessions[slug] = {
task: taskHint || 'Starting...',
sessionUUID,
phase: 'starting',
progress: '0/0',
effort: effortStr || 'E1',
mode: 'starting',
started: timestamp,
updatedAt: timestamp,
currentMode: 'algorithm',
modeHistory: [{ mode: 'algorithm', startedAt: Date.now() }],
ratings: [],
minimalCount: 0,
};
writeRegistry(registry);
} catch {}
}

/**
* Add a RatingPulse to a session in work.json. Called by PromptProcessing fast-path.
* If sessionUUID matches an existing session, appends to its ratings array and increments minimalCount.
Expand Down
52 changes: 0 additions & 52 deletions LifeOS/install/hooks/lib/tab-setter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,58 +340,6 @@ export function setTabState(opts: SetTabOptions): void {
cleanupStaleStateFiles();
}

/**
* Set ONLY the leading mode/tier token ("N" | "E1".."E5") on the current tab,
* preserving the working description, and clearing any stale completion state.
*
* This is the authoritative mode-token writer, called by TheRouter the moment
* it classifies the turn — so the tab projects the real {mode,tier} decision
* instead of PromptProcessing's shadow-classifier guess. It is deliberately a
* distinct primitive from setTabState (which takes a full title) and setPhaseTab
* (which needs an Algorithm phase): here we mutate ONLY the token, keep the
* description, and drop a prior turn's `✅ done` so it can't linger into live work.
*
* - `token`: "N" for NATIVE turns, "E1".."E5" for ALGORITHM. (MINIMAL passes no call.)
* - `fallbackDesc`: used only when the current title is absent or a stale completion
* (whose description we intentionally drop). Normally PromptProcessing's ~50ms
* deterministic stamp has already set a live working description we preserve.
*
* Silent no-op when no kitty socket/session resolves (setTabState handles that).
* Never writes stdout — safe to call from a hook that emits JSON on stdout.
*/
const LIVE_ALGO_PHASES = new Set(['OBSERVE', 'THINK', 'PLAN', 'BUILD', 'EXECUTE', 'VERIFY', 'LEARN']);

export function setModeToken(sessionId: string | undefined, token: string, fallbackDesc?: string): void {
if (!token) return;
try {
const cur = readTabState(sessionId);
const wasCompleted = !!cur && (cur.state === 'completed' || cur.state === 'idle' || /✅/.test(cur.title || ''));

// Mid-run follow-up on an ALGORITHM turn: the tab is already showing a live
// phase (e.g. "E4 👁️ desc"). Preserve the phase icon + phase field, swapping
// ONLY the tier token — delegate to setPhaseTab. Without this, re-stamping the
// token every turn would revert 👁️→⚙️ and drop the phase (the documented
// "orange gear wiped the phase tab" regression). Only for E-tier tokens: a
// NATIVE ('N') follow-up after an algorithm turn SHOULD clear the phase → falls
// through to the neutral stamp below.
if (cur && !wasCompleted && cur.phase && LIVE_ALGO_PHASES.has(cur.phase) && /^E[1-5]$/.test(token)) {
const carriedDesc = stripPrefix(cur.title);
setPhaseTab(cur.phase as AlgorithmTabPhase, sessionId!, carriedDesc || undefined, token);
return;
}

// Preserve a LIVE working description; drop a stale completion's text.
let desc = cur && !wasCompleted ? stripPrefix(cur.title) : '';
if (!desc) desc = (fallbackDesc && fallbackDesc.trim()) || getSessionOneWord(sessionId || '') || 'working…';
const state: TabState = token === 'N' ? 'native' : 'working';
// Neutral working gear at turn start (pre-phase); phase icons (👁️📋…) arrive from
// setPhaseTab. Title already carries the token, so don't also pass modeToken.
setTabState({ title: `${token} ⚙️ ${desc}`, state, sessionId });
} catch (err) {
console.error('[tab-setter] setModeToken failed:', err);
}
}

/**
* Read per-window state file. Returns null if not found or invalid.
*/
Expand Down