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
2 changes: 2 additions & 0 deletions packages/cli/src/exec-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ function describePermissionScope(scope: AskPermissionScope): string {
return "MCP tool access";
case "unknown":
return "unclassified side effects";
default:
return `scope: ${scope}`;
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/tests/exec-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function createSettings(
reasoningEffort: "high",
debugLogEnabled: false,
telemetryEnabled: false,
compressThreshold: 0.8,
permissions,
enabledSkills: {},
statusline: { enabled: false, refreshMs: 1000, separator: " | ", providers: [] },
Expand Down
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,7 +4,6 @@ export type SlashCommandKind =
| "skill"
| "skills"
| "model"
| "plan"
| "new"
| "init"
| "resume"
Expand All @@ -13,6 +12,8 @@ export type SlashCommandKind =
| "undo"
| "mcp"
| "raw"
| "compact"
| "context"
| "exit";

export type SlashCommandItem = {
Expand All @@ -37,12 +38,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 @@ -92,6 +87,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
90 changes: 55 additions & 35 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 @@ -256,8 +255,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
setActiveStatus(null);
setActiveAskPermissions(undefined);
setPendingPermissionReply(null);
setPlanMode(false);
setPendingPlanImplementation(null);
setDismissedQuestionIds(new Set());
await resetStaticView([]);
await refreshSkills();
Expand Down Expand Up @@ -392,6 +389,61 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
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 @@ -400,7 +452,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
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 @@ -436,12 +487,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
}
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 Down Expand Up @@ -537,25 +582,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
[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 @@ -577,8 +603,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
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 @@ -1043,8 +1067,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
onSubmit={handlePermissionResult}
onCancel={handlePermissionCancel}
/>
) : pendingPlanImplementation && !busy ? (
<PlanImplementationPrompt onSelect={handlePlanImplementationChoice} />
) : isExiting ? null : (
<PromptInput
projectRoot={projectRoot}
Expand All @@ -1066,8 +1088,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
placeholder="Type your message..."
statusLineSegments={statusLineSegments}
statusLineSeparator={resolvedSettings.statusline.separator}
planMode={planMode}
onPlanModeChange={setPlanMode}
/>
)}
</Box>
Expand Down
36 changes: 12 additions & 24 deletions packages/cli/src/ui/views/PromptInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export type PromptSubmission = {
permissions?: UserToolPermission[];
alwaysAllows?: PermissionScope[];
planMode?: boolean;
command?: "new" | "resume" | "fork" | "continue" | "undo" | "mcp" | "exit";
command?: "new" | "resume" | "fork" | "continue" | "undo" | "mcp" | "compact" | "context" | "exit";
};

export type PromptDraft = {
Expand All @@ -97,11 +97,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 +130,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 +227,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 +430,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 +679,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 @@ -729,6 +714,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 @@ -765,7 +760,6 @@ export const PromptInput = React.memo(function PromptInput({
text: expandPasteMarkers(buffer.text, pastesRef.current),
imageUrls,
selectedSkills,
planMode,
});
resetPromptInput();
}
Expand Down Expand Up @@ -803,12 +797,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