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
21 changes: 14 additions & 7 deletions packages/cli/src/ui/core/slash-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ export type SlashCommandKind =
| "skill"
| "skills"
| "model"
| "plan"
| "new"
| "init"
| "resume"
| "continue"
| "undo"
| "mcp"
| "raw"
| "compact"
| "context"
| "exit";

export type SlashCommandItem = {
Expand All @@ -36,12 +37,6 @@ export const BUILTIN_SLASH_COMMANDS: SlashCommandItem[] = [
label: "/model",
description: "Select model, thinking mode and effort control",
},
{
kind: "plan",
name: "plan",
label: "/plan",
description: "Switch the input to Plan Mode",
},
{
kind: "new",
name: "new",
Expand Down Expand Up @@ -85,6 +80,18 @@ export const BUILTIN_SLASH_COMMANDS: SlashCommandItem[] = [
args: ["lite", "normal", "raw-scrollback"],
description: "Toggle display mode for viewing or collapsing reasoning content",
},
{
kind: "compact",
name: "compact",
label: "/compact",
description: "Compress conversation context to reduce token usage",
},
{
kind: "context",
name: "context",
label: "/context",
description: "Show current conversation token usage and context stats",
},
{
kind: "exit",
name: "exit",
Expand Down
87 changes: 49 additions & 38 deletions packages/cli/src/ui/views/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
formatAskUserQuestionAnswers,
} from "../core/ask-user-question";
import { PermissionPrompt, type PermissionPromptResult } from "./PermissionPrompt";
import { PlanImplementationPrompt, extractProposedPlan, getImplementationPrompt } from "./PlanImplementationPrompt";
import { buildExitSummaryText, buildResumeHintText } from "../exit-summary";
import { RawMode, useRawModeContext } from "../contexts";
import { renderMessageToStdout } from "../components/MessageView/utils";
Expand Down Expand Up @@ -134,8 +133,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
const [nowTick, setNowTick] = useState(0);
const [mcpStatuses, setMcpStatuses] = useState<ReturnType<typeof sessionManager.getMcpStatus>>([]);
const [showProcessStdout, setShowProcessStdout] = useState(false);
const [planMode, setPlanMode] = useState(false);
const [pendingPlanImplementation, setPendingPlanImplementation] = useState<string | null>(null);

rawModeRef.current = mode;
messagesRef.current = messages;
Expand Down Expand Up @@ -256,8 +253,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
setActiveStatus(null);
setActiveAskPermissions(undefined);
setPendingPermissionReply(null);
setPlanMode(false);
setPendingPlanImplementation(null);
setDismissedQuestionIds(new Set());
await resetStaticView([]);
await refreshSkills();
Expand Down Expand Up @@ -366,6 +361,55 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
navigateToSubView("mcp-status");
return;
}
if (submission.command === "compact") {
const activeSessionId = sessionManager.getActiveSessionId();
if (!activeSessionId) {
setErrorLine("No active session to compact.");
return;
}
setBusy(true);
sessionManager.addSessionSystemMessage(
activeSessionId,
"Compacting conversation context to reduce token usage...",
true,
{ asThinking: true }
);
sessionManager.compactSession(activeSessionId).then(() => {
setBusy(false);
refreshSessionsList();
}).catch((err) => {
setBusy(false);
setErrorLine(`Compact failed: ${err instanceof Error ? err.message : String(err)}`);
});
return;
}
if (submission.command === "context") {
const sessionInfo = getSessionInfo();
if (!sessionInfo || !sessionInfo.activeSessionId) {
setErrorLine("No active session.");
return;
}
const pct = sessionInfo.maxContextTokens > 0
? Math.round((sessionInfo.activeTokens / sessionInfo.maxContextTokens) * 100)
: 0;
const summary = [
`Model: ${sessionInfo.model}`,
`Messages: ${sessionInfo.messageCount}`,
`API requests: ${sessionInfo.requestCount}`,
`Active tokens: ${sessionInfo.activeTokens.toLocaleString()} / ${sessionInfo.maxContextTokens.toLocaleString()} (${pct}%)`,
`Total tokens used: ${sessionInfo.totalTokens.toLocaleString()}`,
];
if (Object.keys(sessionInfo.toolUsage).length > 0) {
const tools = Object.entries(sessionInfo.toolUsage)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([name, count]) => ` ${name}: ${count}x`)
.join("\n");
summary.push(`\nTop tools:\n${tools}`);
}
sessionManager.addSessionSystemMessage(sessionInfo.activeSessionId, summary.join("\n"), true, { asThinking: true });
return;
}

const prompt: UserPromptContent = {
text: submission.text,
Expand All @@ -374,7 +418,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
submission.selectedSkills && submission.selectedSkills.length > 0 ? submission.selectedSkills : undefined,
permissions: submission.permissions,
alwaysAllows: submission.alwaysAllows,
planMode: submission.planMode ?? planMode,
};
const activeSessionId = sessionManager.getActiveSessionId();
const permissionReply =
Expand Down Expand Up @@ -410,12 +453,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
}
await refreshSkills();
refreshSessionsList();
const completedSession = sessionManager.getSession(sessionManager.getActiveSessionId() ?? "");
const proposedPlan =
prompt.planMode && completedSession?.status === "completed"
? extractProposedPlan(completedSession.assistantReply)
: null;
setPendingPlanImplementation(proposedPlan);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
setErrorLine(message);
Expand All @@ -437,7 +474,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
refreshSessionsList,
navigateToSubView,
resetToWelcome,
planMode,
]
);

Expand Down Expand Up @@ -509,25 +545,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
[handlePrompt]
);

const handlePlanImplementationChoice = useCallback(
(choice: "implement" | "stay" | "default") => {
const proposedPlan = pendingPlanImplementation;
setPendingPlanImplementation(null);
if (choice === "stay") {
return;
}
setPlanMode(false);
if (choice === "implement" && proposedPlan) {
handleSubmit({
text: getImplementationPrompt(proposedPlan),
imageUrls: [],
planMode: false,
});
}
},
[handleSubmit, pendingPlanImplementation]
);

const handleExitShortcut = useCallback(() => {
handleExit({ showCommand: false, showSummary: false });
}, [handleExit]);
Expand All @@ -549,8 +566,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
setRunningProcesses(session?.processes ?? null);
setActiveStatus(session?.status ?? null);
setActiveAskPermissions(session?.askPermissions);
setPlanMode(session?.planMode === true);
setPendingPlanImplementation(null);
if (pendingPermissionReply && pendingPermissionReply.sessionId !== sessionId) {
setPendingPermissionReply(null);
}
Expand Down Expand Up @@ -999,8 +1014,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
onSubmit={handlePermissionResult}
onCancel={handlePermissionCancel}
/>
) : pendingPlanImplementation && !busy ? (
<PlanImplementationPrompt onSelect={handlePlanImplementationChoice} />
) : isExiting ? null : (
<PromptInput
projectRoot={projectRoot}
Expand All @@ -1022,8 +1035,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
placeholder="Type your message..."
statusLineSegments={statusLineSegments}
statusLineSeparator={resolvedSettings.statusline.separator}
planMode={planMode}
onPlanModeChange={setPlanMode}
/>
)}
</Box>
Expand Down
37 changes: 12 additions & 25 deletions packages/cli/src/ui/views/PromptInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ export type PromptSubmission = {
selectedSkills?: SkillInfo[];
permissions?: UserToolPermission[];
alwaysAllows?: PermissionScope[];
planMode?: boolean;
command?: "new" | "resume" | "continue" | "undo" | "mcp" | "exit";
command?: "new" | "resume" | "continue" | "undo" | "mcp" | "compact" | "context" | "exit";
};

export type PromptDraft = {
Expand All @@ -97,11 +96,9 @@ type Props = {
promptDraft?: PromptDraft | null;
statusLineSegments?: StatusSegment[];
statusLineSeparator?: string;
planMode: boolean;
onSubmit: (submission: PromptSubmission) => void;
onModelConfigChange: (selection: ModelConfigSelection) => string | Promise<string>;
onRawModeChange?: (mode: string) => void;
onPlanModeChange: (enabled: boolean) => void;
onInterrupt: () => void;
onToggleProcessStdout?: () => void;
onExitShortcut?: () => void;
Expand Down Expand Up @@ -132,14 +129,12 @@ export const PromptInput = React.memo(function PromptInput({
promptDraft,
statusLineSegments,
statusLineSeparator,
planMode,
onSubmit,
onModelConfigChange,
onInterrupt,
onToggleProcessStdout,
onExitShortcut,
onRawModeChange,
onPlanModeChange,
}: Props): React.ReactElement {
const { stdout } = useStdout();
const inputTextRef = useRef<DOMElement | null>(null);
Expand Down Expand Up @@ -231,10 +226,9 @@ export const PromptInput = React.memo(function PromptInput({
screenWidth,
cursorLayoutKey ?? "default",
imageUrls.length,
planMode ? "plan-mode" : "default-mode",
selectedSkills.map((skill) => skill.name).join("\u001F"),
].join("\u001E"),
[cursorLayoutKey, imageUrls.length, planMode, screenWidth, selectedSkills]
[cursorLayoutKey, imageUrls.length, screenWidth, selectedSkills]
);
useTerminalFocusReporting(stdout, !disabled);
useTerminalExtendedKeys(stdout, !disabled);
Expand Down Expand Up @@ -435,11 +429,6 @@ export const PromptInput = React.memo(function PromptInput({
const returnAction = getPromptReturnKeyAction(key);
const isPlainReturn = returnAction === "submit";

if (key.shift && key.tab) {
onPlanModeChange(!planMode);
return;
}

if (showFileMentionMenu) {
if (key.upArrow || key.downArrow || key.tab || returnAction === "submit") {
return;
Expand Down Expand Up @@ -689,11 +678,6 @@ export const PromptInput = React.memo(function PromptInput({
setShowModelDropdown(true);
return;
}
if (item.kind === "plan") {
clearSlashToken();
onPlanModeChange(true);
return;
}
if (item.kind === "raw") {
clearSlashToken();
setOpenRawModelDropdown(true);
Expand Down Expand Up @@ -724,6 +708,16 @@ export const PromptInput = React.memo(function PromptInput({
resetPromptInput();
return;
}
if (item.kind === "compact") {
onSubmit({ text: "/compact", imageUrls: [], command: "compact" });
resetPromptInput();
return;
}
if (item.kind === "context") {
onSubmit({ text: "/context", imageUrls: [], command: "context" });
resetPromptInput();
return;
}
if (item.kind === "mcp") {
onSubmit({ text: "/mcp", imageUrls: [], command: "mcp" });
resetPromptInput();
Expand Down Expand Up @@ -760,7 +754,6 @@ export const PromptInput = React.memo(function PromptInput({
text: expandPasteMarkers(buffer.text, pastesRef.current),
imageUrls,
selectedSkills,
planMode,
});
resetPromptInput();
}
Expand Down Expand Up @@ -798,12 +791,6 @@ export const PromptInput = React.memo(function PromptInput({
<Text dimColor> (use /skills to edit)</Text>
</Box>
) : null}
{planMode ? (
<Box width={screenWidth} justifyContent="flex-end">
<Text color="yellow">💡 Plan mode</Text>
<Text dimColor> (shift+tab to cycle)</Text>
</Box>
) : null}
{/* Input */}
<Box
width={screenWidth}
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vegamo/deepcode-core",
"version": "0.1.34",
"version": "0.1.33",
"description": "Deep Code core library — LLM session management, tool execution, and shared utilities",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -33,6 +33,7 @@
"gray-matter": "^4.0.3",
"ignore": "^7.0.5",
"openai": "^6.35.0",
"sql.js": "^1.14.1",
"undici": "^7.25.0",
"zod": "^4.4.3"
}
Expand Down
Loading