feat(chat): /summary slash command for non-destructive session summaries
Typing /summary (optionally with a topic hint) produces a structured summary as a normal assistant message — without compacting or mutating session history. Reuses the existing "Start new session from this answer" button for hand-off. Prompts are customizable in Settings → Magic Prompts under a new Session group. Autocomplete shows an 'openchamber' badge to distinguish our commands from OpenCode-built-in ones.
This commit is contained in:
@@ -28,6 +28,7 @@ import * as sessionActions from '@/sync/session-actions';
|
||||
import { useUserMessageHistory } from '@/sync/sync-context';
|
||||
import { useInlineCommentDraftStore, type InlineCommentDraft } from '@/stores/useInlineCommentDraftStore';
|
||||
import { appendInlineComments } from '@/lib/messages/inlineComments';
|
||||
import { renderMagicPrompt } from '@/lib/magicPrompts';
|
||||
import { AttachedFilesList } from './FileAttachment';
|
||||
import { QueuedMessageChips } from './QueuedMessageChips';
|
||||
import { FileMentionAutocomplete, type FileMentionHandle } from './FileMentionAutocomplete';
|
||||
@@ -1533,6 +1534,35 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (commandName === 'summary' && currentSessionId) {
|
||||
try {
|
||||
await sessionActions.waitForConnectionOrThrow();
|
||||
// Everything after `/summary ` is an optional topic hint
|
||||
// the user wants the summary focused on.
|
||||
const topic = normalizedCommand.replace(/^\/summary\b/i, '').trim();
|
||||
const topicLine = topic ? ` focused on: ${topic}` : '';
|
||||
const topicBlock = topic
|
||||
? `The user asked you to focus this summary on: ${topic}. Prioritize that topic; mention unrelated threads only in passing.`
|
||||
: '';
|
||||
const visibleText = await renderMagicPrompt('session.summary.visible', { topic_line: topicLine });
|
||||
const instructionsText = await renderMagicPrompt('session.summary.instructions', { topic_block: topicBlock });
|
||||
await sendMessage(
|
||||
visibleText,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
currentAgentName,
|
||||
[],
|
||||
agentMentionName,
|
||||
[{ text: instructionsText, synthetic: true }],
|
||||
currentVariant,
|
||||
inputMode,
|
||||
);
|
||||
scrollToBottom?.({ instant: true, force: true });
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to generate summary');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all attachments for error recovery
|
||||
|
||||
@@ -13,6 +13,7 @@ interface CommandInfo {
|
||||
agent?: string;
|
||||
model?: string;
|
||||
isBuiltIn?: boolean;
|
||||
isOpenChamber?: boolean;
|
||||
isSkill?: boolean;
|
||||
scope?: string;
|
||||
}
|
||||
@@ -111,6 +112,10 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
||||
: []
|
||||
),
|
||||
{ name: 'compact', description: 'Compress session history using AI to reduce context size', isBuiltIn: true },
|
||||
...(hasSession
|
||||
? [{ name: 'summary', description: 'Non-destructive session summary. Optional topic hint after the command.', isOpenChamber: true }]
|
||||
: []
|
||||
),
|
||||
];
|
||||
|
||||
const commandMap = new Map<string, CommandInfo>();
|
||||
@@ -154,6 +159,10 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
||||
: []
|
||||
),
|
||||
{ name: 'compact', description: 'Compress session history using AI to reduce context size', isBuiltIn: true },
|
||||
...(hasSession
|
||||
? [{ name: 'summary', description: 'Non-destructive session summary. Optional topic hint after the command.', isOpenChamber: true }]
|
||||
: []
|
||||
),
|
||||
];
|
||||
|
||||
const filtered = (searchQuery
|
||||
@@ -293,6 +302,7 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
||||
<div>
|
||||
{commands.map((command, index) => {
|
||||
const isSystem = command.isBuiltIn;
|
||||
const isOpenChamberBadge = command.isOpenChamber;
|
||||
const isProject = command.scope === 'project';
|
||||
|
||||
return (
|
||||
@@ -359,7 +369,18 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
||||
skill
|
||||
</span>
|
||||
) : null}
|
||||
{isSystem ? (
|
||||
{isOpenChamberBadge ? (
|
||||
<span
|
||||
className="text-[10px] leading-none uppercase font-bold tracking-tight px-1.5 py-1 rounded border flex-shrink-0"
|
||||
style={{
|
||||
backgroundColor: 'color-mix(in srgb, var(--primary-base) 14%, transparent)',
|
||||
color: 'var(--primary-base)',
|
||||
borderColor: 'color-mix(in srgb, var(--primary-base) 28%, transparent)',
|
||||
}}
|
||||
>
|
||||
openchamber
|
||||
</span>
|
||||
) : isSystem ? (
|
||||
<span className="text-[10px] leading-none uppercase font-bold tracking-tight bg-[var(--status-warning-background)] text-[var(--status-warning)] border-[var(--status-warning-border)] px-1.5 py-1 rounded border flex-shrink-0">
|
||||
system
|
||||
</span>
|
||||
|
||||
@@ -123,6 +123,14 @@ const PROMPT_PAGE_MAP: Record<string, PromptPageConfig> = {
|
||||
{ id: 'plan.implement.instructions', title: 'Instructions' },
|
||||
],
|
||||
},
|
||||
'session.summary': {
|
||||
title: 'Session Summary',
|
||||
description: 'Prompts used by the /summary slash command: visible user message + hidden instructions. Non-destructive — does not compact session history.',
|
||||
blocks: [
|
||||
{ id: 'session.summary.visible', title: 'Visible Prompt' },
|
||||
{ id: 'session.summary.instructions', title: 'Instructions' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const hasOwn = (input: Record<string, string>, key: string) => Object.prototype.hasOwnProperty.call(input, key);
|
||||
|
||||
@@ -40,6 +40,12 @@ export const MagicPromptsSidebar: React.FC<MagicPromptsSidebarProps> = ({ onItem
|
||||
{ id: 'plan.implement', title: 'Implement Plan' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Session',
|
||||
items: [
|
||||
{ id: 'session.summary', title: 'Session Summary' },
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user