diff --git a/LifeOS/install/hooks/lib/isa-utils.ts b/LifeOS/install/hooks/lib/isa-utils.ts index 15fe9edc4e..6a3a85c10c 100755 --- a/LifeOS/install/hooks/lib/isa-utils.ts +++ b/LifeOS/install/hooks/lib/isa-utils.ts @@ -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. diff --git a/LifeOS/install/hooks/lib/tab-setter.ts b/LifeOS/install/hooks/lib/tab-setter.ts index d4f9311020..39f9b1b9fd 100755 --- a/LifeOS/install/hooks/lib/tab-setter.ts +++ b/LifeOS/install/hooks/lib/tab-setter.ts @@ -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. */