2026-04-06 20:44:13 +03:00
|
|
|
import React from 'react';
|
2026-05-21 15:45:15 +03:00
|
|
|
import type { Message, Part } from '@opencode-ai/sdk/v2';
|
2026-04-06 20:44:13 +03:00
|
|
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
|
|
|
|
import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
|
|
|
|
|
|
2026-05-21 15:45:15 +03:00
|
|
|
type SessionMessageRecord = { info: Message; parts: Part[] };
|
|
|
|
|
|
2026-04-06 20:44:13 +03:00
|
|
|
/**
|
|
|
|
|
* Watches session messages for plan creation and marks sessions as plan-available.
|
|
|
|
|
*
|
|
|
|
|
* This is the single source of truth for plan detection. When a plan_enter tool
|
|
|
|
|
* executes, it creates a synthetic message like "The plan at ${path}" or
|
|
|
|
|
* "User has requested to enter plan mode". We detect these and signal availability.
|
|
|
|
|
*
|
|
|
|
|
* The Header component subscribes to sessionPlanAvailable map to show/hide the Plan tab.
|
|
|
|
|
*/
|
2026-05-21 15:45:15 +03:00
|
|
|
export const usePlanDetection = (sessionId: string, messageRecords: SessionMessageRecord[]) => {
|
2026-04-06 20:44:13 +03:00
|
|
|
const planModeEnabled = useFeatureFlagsStore((state) => state.planModeEnabled);
|
|
|
|
|
const markSessionPlanAvailable = useSessionUIStore((state) => state.markSessionPlanAvailable);
|
|
|
|
|
const isSessionPlanAvailable = useSessionUIStore((state) => state.isSessionPlanAvailable);
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
// Early exit if plan mode is disabled - don't parse messages
|
|
|
|
|
if (!planModeEnabled) return;
|
|
|
|
|
if (!sessionId) return;
|
|
|
|
|
|
|
|
|
|
// Already marked as available - no need to check again
|
|
|
|
|
if (isSessionPlanAvailable(sessionId)) return;
|
|
|
|
|
|
2026-05-21 15:45:15 +03:00
|
|
|
// Scan the already-materialized message records used by ChatContainer so
|
|
|
|
|
// plan detection does not add a second active-session message subscription.
|
|
|
|
|
for (const message of messageRecords) {
|
2026-04-06 20:44:13 +03:00
|
|
|
// Only check assistant messages for plan references
|
2026-05-21 15:45:15 +03:00
|
|
|
if (message.info.role !== 'assistant') continue;
|
2026-04-06 20:44:13 +03:00
|
|
|
|
2026-05-21 15:45:15 +03:00
|
|
|
for (const part of message.parts) {
|
|
|
|
|
const record = part as { type?: string; text?: string };
|
|
|
|
|
if (record.type !== 'text') continue;
|
|
|
|
|
const text = record.text || '';
|
2026-04-06 20:44:13 +03:00
|
|
|
|
|
|
|
|
// Check for plan file reference in synthetic messages
|
|
|
|
|
if (text.includes('The plan at ') || text.includes('User has requested to enter plan mode')) {
|
|
|
|
|
markSessionPlanAvailable(sessionId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-21 15:45:15 +03:00
|
|
|
}, [planModeEnabled, sessionId, messageRecords, markSessionPlanAvailable, isSessionPlanAvailable]);
|
2026-04-06 20:44:13 +03:00
|
|
|
};
|