fix: scope session assist to session directory

This commit is contained in:
Bohdan Triapitsyn
2026-07-06 20:00:53 +03:00
parent 18d1c9111a
commit 1824b51155
6 changed files with 52 additions and 12 deletions
@@ -267,7 +267,7 @@ const ChatViewport = React.memo(({
</div>
)}
<SessionRecapNote sessionId={currentSessionId} isMobile={isMobile} />
<SessionRecapNote sessionId={currentSessionId} directory={directory} isMobile={isMobile} />
<div className="mb-3">
<StatusRowContainer />
@@ -4758,6 +4758,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
<div className="flex flex-col">
<SessionSuggestionChip
sessionId={currentSessionId}
directory={currentSessionDirectoryForSync ?? currentDirectory}
hidden={hasContent || newSessionDraftOpen}
onApply={applyAssistSuggestion}
className="mb-1.5"
@@ -4841,6 +4842,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
<>
<SessionSuggestionChip
sessionId={currentSessionId}
directory={currentSessionDirectoryForSync ?? currentDirectory}
hidden={hasContent || newSessionDraftOpen}
onApply={applyAssistSuggestion}
className="mb-1.5"
@@ -4,14 +4,15 @@ import { useI18n } from '@/lib/i18n';
interface SessionRecapNoteProps {
sessionId: string;
directory?: string;
isMobile: boolean;
}
// Quiet one-paragraph recap of the agent's last reply, rendered right under
// the last message (above the reserved bottom gap). Appears only after the
// 5-minute quiet window, so the layout shift happens off-screen in practice.
export const SessionRecapNote: React.FC<SessionRecapNoteProps> = React.memo(({ sessionId, isMobile }) => {
const { visibleRecap } = useSessionAssistState(sessionId);
export const SessionRecapNote: React.FC<SessionRecapNoteProps> = React.memo(({ sessionId, directory, isMobile }) => {
const { visibleRecap } = useSessionAssistState(sessionId, directory);
const { t } = useI18n();
if (!visibleRecap) {
@@ -8,6 +8,7 @@ import { useI18n } from '@/lib/i18n';
interface SessionSuggestionChipProps {
sessionId: string | null;
directory?: string;
/** The composer already has content — the suggestion must stay out of the way. */
hidden: boolean;
onApply: (text: string) => void;
@@ -20,8 +21,8 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
// One small-model-suggested follow-up message, styled like the draft starter
// chips. Tapping it fills the composer (no auto-send); the X patches the
// suggestion out of the session metadata so it stays dismissed everywhere.
export const SessionSuggestionChip: React.FC<SessionSuggestionChipProps> = React.memo(({ sessionId, hidden, onApply, className }) => {
const { suggestion } = useSessionAssistState(sessionId ?? '');
export const SessionSuggestionChip: React.FC<SessionSuggestionChipProps> = React.memo(({ sessionId, directory, hidden, onApply, className }) => {
const { suggestion } = useSessionAssistState(sessionId ?? '', directory);
const { t } = useI18n();
const { currentTheme } = useThemeSystem();
const [dismissing, setDismissing] = React.useState(false);
+6 -6
View File
@@ -13,8 +13,8 @@ interface LastMessageSnapshot {
}
/** Narrow subscription to the last message of a session (id/role/time only). */
function useLastMessageSnapshot(sessionId: string): LastMessageSnapshot | null {
const store = useDirectoryStore();
function useLastMessageSnapshot(sessionId: string, directory?: string): LastMessageSnapshot | null {
const store = useDirectoryStore(directory);
const cacheRef = React.useRef<LastMessageSnapshot | null>(null);
const getSnapshot = React.useCallback((): LastMessageSnapshot | null => {
@@ -56,10 +56,10 @@ export interface SessionAssistState {
suggestion: string | null;
}
export function useSessionAssistState(sessionId: string): SessionAssistState {
const session = useSession(sessionId);
const status = useSessionStatus(sessionId);
const lastMessage = useLastMessageSnapshot(sessionId);
export function useSessionAssistState(sessionId: string, directory?: string): SessionAssistState {
const session = useSession(sessionId, directory);
const status = useSessionStatus(sessionId, directory);
const lastMessage = useLastMessageSnapshot(sessionId, directory);
const isIdle = !status || status.type === 'idle';
const payload = getSessionAssist(session);
@@ -43,7 +43,43 @@ const ASSIST_SYSTEM_PROMPT = [
'You assist a user who chats with a coding agent. Based on the conversation transcript, return exactly one JSON object and nothing else — no prose, no markdown, no code fences.',
'Shape: {"recap": string, "suggestion": string}',
'recap: at most 20 words. State the substance directly — the facts, result, or conclusion, plus the next move if there is one. NEVER narrate ("The assistant explained…", "The agent did…") — write the content itself, like a note the user jotted down.',
'suggestion: the next message to send in this conversation, addressed TO the agent — a concise instruction or question that moves the work forward, e.g. "Run the tests and fix failures" / "Commit this". Imperative or question form. Never explain, never offer help, never say "you can".',
'suggestion: write ONE immediately sendable next user message addressed TO the coding agent.',
'The suggestion should be the most useful next step after the assistant\'s latest reply. It should help the user continue productively, not inspect already-known details.',
'Prefer suggestions that ask the agent to make a concrete improvement, implement something specific, validate the latest change, explain tradeoffs, improve the current approach, or continue from the current result.',
'Rules for suggestion:',
'- Output exactly one message the user could click and send without editing.',
'- Pick one best next action yourself.',
'- Do not include alternatives, choices, slash-separated options, or "or".',
'- Do not write "Do X or Y", "Ask whether...", "Maybe...", or "You could...".',
'- Do not ask for information the assistant already provided.',
'- Do not ask to see exact code, file paths, prompt locations, or implementation internals unless the assistant did not provide them and they are necessary for the next step.',
'- Do not produce generic workflow commands like "Run tests" unless testing is clearly the next unresolved step.',
'- Do not produce meta/debug requests that merely inspect the implementation.',
'- Use imperative or question form.',
'- Keep it concise.',
'Use these examples to understand how to choose the suggestion. Do not copy their topic or wording unless the current conversation is about the same thing.',
'Example 1:',
'Assistant reply summary:',
'The assistant already identified the file where the feature is implemented, explained what context is sent to the small model, and summarized the current prompt.',
'Bad suggestion:',
'"Show me the exact runtime.js code and where the prompt is built."',
'Why bad:',
'It asks for information the assistant already provided. It repeats inspection instead of moving to an improvement or decision.',
'Good suggestion:',
'"Suggest how to improve the prompt and context so the generated suggestion is more useful."',
'Why good:',
'It naturally continues from the analysis and asks for a concrete improvement.',
'Example 2:',
'Assistant reply summary:',
'The assistant implemented a timeline dialog redesign, listed concrete UI changes, and reported that type-check and lint passed.',
'Bad suggestion:',
'"Check whether scrolling or loading older messages works without jumps."',
'Why bad:',
'It contains an alternative. A suggestion chip must be one sendable message, not a choice the user has to edit.',
'Good suggestion:',
'"Check whether scrolling and loading older messages work without jumps."',
'Why good:',
'It picks a single validation request that the user can send immediately.',
'Both values MUST be written in the same language as the conversation text itself. Ignore any other language preferences or personalization you may have — only the conversation text decides the language.',
'Use double quotes for JSON strings, no trailing commas.',
].join('\n');