Skip to content

Commit ae41320

Browse files
feat: 全面架构升级 - Permission Profile / SQLite 日志 / Job 队列 / Skill 标准化
1 parent 5e0fbc5 commit ae41320

23 files changed

Lines changed: 3402 additions & 167 deletions

packages/cli/src/ui/core/slash-commands.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ export type SlashCommandKind =
44
| "skill"
55
| "skills"
66
| "model"
7-
| "plan"
87
| "new"
98
| "init"
109
| "resume"
1110
| "continue"
1211
| "undo"
1312
| "mcp"
1413
| "raw"
14+
| "compact"
15+
| "context"
1516
| "exit";
1617

1718
export type SlashCommandItem = {
@@ -36,12 +37,6 @@ export const BUILTIN_SLASH_COMMANDS: SlashCommandItem[] = [
3637
label: "/model",
3738
description: "Select model, thinking mode and effort control",
3839
},
39-
{
40-
kind: "plan",
41-
name: "plan",
42-
label: "/plan",
43-
description: "Switch the input to Plan Mode",
44-
},
4540
{
4641
kind: "new",
4742
name: "new",
@@ -85,6 +80,18 @@ export const BUILTIN_SLASH_COMMANDS: SlashCommandItem[] = [
8580
args: ["lite", "normal", "raw-scrollback"],
8681
description: "Toggle display mode for viewing or collapsing reasoning content",
8782
},
83+
{
84+
kind: "compact",
85+
name: "compact",
86+
label: "/compact",
87+
description: "Compress conversation context to reduce token usage",
88+
},
89+
{
90+
kind: "context",
91+
name: "context",
92+
label: "/context",
93+
description: "Show current conversation token usage and context stats",
94+
},
8895
{
8996
kind: "exit",
9097
name: "exit",

packages/cli/src/ui/views/App.tsx

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
formatAskUserQuestionAnswers,
2121
} from "../core/ask-user-question";
2222
import { PermissionPrompt, type PermissionPromptResult } from "./PermissionPrompt";
23-
import { PlanImplementationPrompt, extractProposedPlan, getImplementationPrompt } from "./PlanImplementationPrompt";
2423
import { buildExitSummaryText, buildResumeHintText } from "../exit-summary";
2524
import { RawMode, useRawModeContext } from "../contexts";
2625
import { renderMessageToStdout } from "../components/MessageView/utils";
@@ -134,8 +133,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
134133
const [nowTick, setNowTick] = useState(0);
135134
const [mcpStatuses, setMcpStatuses] = useState<ReturnType<typeof sessionManager.getMcpStatus>>([]);
136135
const [showProcessStdout, setShowProcessStdout] = useState(false);
137-
const [planMode, setPlanMode] = useState(false);
138-
const [pendingPlanImplementation, setPendingPlanImplementation] = useState<string | null>(null);
139136

140137
rawModeRef.current = mode;
141138
messagesRef.current = messages;
@@ -256,8 +253,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
256253
setActiveStatus(null);
257254
setActiveAskPermissions(undefined);
258255
setPendingPermissionReply(null);
259-
setPlanMode(false);
260-
setPendingPlanImplementation(null);
261256
setDismissedQuestionIds(new Set());
262257
await resetStaticView([]);
263258
await refreshSkills();
@@ -366,6 +361,55 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
366361
navigateToSubView("mcp-status");
367362
return;
368363
}
364+
if (submission.command === "compact") {
365+
const activeSessionId = sessionManager.getActiveSessionId();
366+
if (!activeSessionId) {
367+
setErrorLine("No active session to compact.");
368+
return;
369+
}
370+
setBusy(true);
371+
sessionManager.addSessionSystemMessage(
372+
activeSessionId,
373+
"Compacting conversation context to reduce token usage...",
374+
true,
375+
{ asThinking: true }
376+
);
377+
sessionManager.compactSession(activeSessionId).then(() => {
378+
setBusy(false);
379+
refreshSessionsList();
380+
}).catch((err) => {
381+
setBusy(false);
382+
setErrorLine(`Compact failed: ${err instanceof Error ? err.message : String(err)}`);
383+
});
384+
return;
385+
}
386+
if (submission.command === "context") {
387+
const sessionInfo = getSessionInfo();
388+
if (!sessionInfo || !sessionInfo.activeSessionId) {
389+
setErrorLine("No active session.");
390+
return;
391+
}
392+
const pct = sessionInfo.maxContextTokens > 0
393+
? Math.round((sessionInfo.activeTokens / sessionInfo.maxContextTokens) * 100)
394+
: 0;
395+
const summary = [
396+
`Model: ${sessionInfo.model}`,
397+
`Messages: ${sessionInfo.messageCount}`,
398+
`API requests: ${sessionInfo.requestCount}`,
399+
`Active tokens: ${sessionInfo.activeTokens.toLocaleString()} / ${sessionInfo.maxContextTokens.toLocaleString()} (${pct}%)`,
400+
`Total tokens used: ${sessionInfo.totalTokens.toLocaleString()}`,
401+
];
402+
if (Object.keys(sessionInfo.toolUsage).length > 0) {
403+
const tools = Object.entries(sessionInfo.toolUsage)
404+
.sort(([, a], [, b]) => b - a)
405+
.slice(0, 5)
406+
.map(([name, count]) => ` ${name}: ${count}x`)
407+
.join("\n");
408+
summary.push(`\nTop tools:\n${tools}`);
409+
}
410+
sessionManager.addSessionSystemMessage(sessionInfo.activeSessionId, summary.join("\n"), true, { asThinking: true });
411+
return;
412+
}
369413

370414
const prompt: UserPromptContent = {
371415
text: submission.text,
@@ -374,7 +418,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
374418
submission.selectedSkills && submission.selectedSkills.length > 0 ? submission.selectedSkills : undefined,
375419
permissions: submission.permissions,
376420
alwaysAllows: submission.alwaysAllows,
377-
planMode: submission.planMode ?? planMode,
378421
};
379422
const activeSessionId = sessionManager.getActiveSessionId();
380423
const permissionReply =
@@ -410,12 +453,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
410453
}
411454
await refreshSkills();
412455
refreshSessionsList();
413-
const completedSession = sessionManager.getSession(sessionManager.getActiveSessionId() ?? "");
414-
const proposedPlan =
415-
prompt.planMode && completedSession?.status === "completed"
416-
? extractProposedPlan(completedSession.assistantReply)
417-
: null;
418-
setPendingPlanImplementation(proposedPlan);
419456
} catch (error) {
420457
const message = error instanceof Error ? error.message : String(error);
421458
setErrorLine(message);
@@ -437,7 +474,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
437474
refreshSessionsList,
438475
navigateToSubView,
439476
resetToWelcome,
440-
planMode,
441477
]
442478
);
443479

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

512-
const handlePlanImplementationChoice = useCallback(
513-
(choice: "implement" | "stay" | "default") => {
514-
const proposedPlan = pendingPlanImplementation;
515-
setPendingPlanImplementation(null);
516-
if (choice === "stay") {
517-
return;
518-
}
519-
setPlanMode(false);
520-
if (choice === "implement" && proposedPlan) {
521-
handleSubmit({
522-
text: getImplementationPrompt(proposedPlan),
523-
imageUrls: [],
524-
planMode: false,
525-
});
526-
}
527-
},
528-
[handleSubmit, pendingPlanImplementation]
529-
);
530-
531548
const handleExitShortcut = useCallback(() => {
532549
handleExit({ showCommand: false, showSummary: false });
533550
}, [handleExit]);
@@ -549,8 +566,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
549566
setRunningProcesses(session?.processes ?? null);
550567
setActiveStatus(session?.status ?? null);
551568
setActiveAskPermissions(session?.askPermissions);
552-
setPlanMode(session?.planMode === true);
553-
setPendingPlanImplementation(null);
554569
if (pendingPermissionReply && pendingPermissionReply.sessionId !== sessionId) {
555570
setPendingPermissionReply(null);
556571
}
@@ -999,8 +1014,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
9991014
onSubmit={handlePermissionResult}
10001015
onCancel={handlePermissionCancel}
10011016
/>
1002-
) : pendingPlanImplementation && !busy ? (
1003-
<PlanImplementationPrompt onSelect={handlePlanImplementationChoice} />
10041017
) : isExiting ? null : (
10051018
<PromptInput
10061019
projectRoot={projectRoot}
@@ -1022,8 +1035,6 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
10221035
placeholder="Type your message..."
10231036
statusLineSegments={statusLineSegments}
10241037
statusLineSeparator={resolvedSettings.statusline.separator}
1025-
planMode={planMode}
1026-
onPlanModeChange={setPlanMode}
10271038
/>
10281039
)}
10291040
</Box>

packages/cli/src/ui/views/PromptInput.tsx

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ export type PromptSubmission = {
7272
selectedSkills?: SkillInfo[];
7373
permissions?: UserToolPermission[];
7474
alwaysAllows?: PermissionScope[];
75-
planMode?: boolean;
76-
command?: "new" | "resume" | "continue" | "undo" | "mcp" | "exit";
75+
command?: "new" | "resume" | "continue" | "undo" | "mcp" | "compact" | "context" | "exit";
7776
};
7877

7978
export type PromptDraft = {
@@ -97,11 +96,9 @@ type Props = {
9796
promptDraft?: PromptDraft | null;
9897
statusLineSegments?: StatusSegment[];
9998
statusLineSeparator?: string;
100-
planMode: boolean;
10199
onSubmit: (submission: PromptSubmission) => void;
102100
onModelConfigChange: (selection: ModelConfigSelection) => string | Promise<string>;
103101
onRawModeChange?: (mode: string) => void;
104-
onPlanModeChange: (enabled: boolean) => void;
105102
onInterrupt: () => void;
106103
onToggleProcessStdout?: () => void;
107104
onExitShortcut?: () => void;
@@ -132,14 +129,12 @@ export const PromptInput = React.memo(function PromptInput({
132129
promptDraft,
133130
statusLineSegments,
134131
statusLineSeparator,
135-
planMode,
136132
onSubmit,
137133
onModelConfigChange,
138134
onInterrupt,
139135
onToggleProcessStdout,
140136
onExitShortcut,
141137
onRawModeChange,
142-
onPlanModeChange,
143138
}: Props): React.ReactElement {
144139
const { stdout } = useStdout();
145140
const inputTextRef = useRef<DOMElement | null>(null);
@@ -231,10 +226,9 @@ export const PromptInput = React.memo(function PromptInput({
231226
screenWidth,
232227
cursorLayoutKey ?? "default",
233228
imageUrls.length,
234-
planMode ? "plan-mode" : "default-mode",
235229
selectedSkills.map((skill) => skill.name).join("\u001F"),
236230
].join("\u001E"),
237-
[cursorLayoutKey, imageUrls.length, planMode, screenWidth, selectedSkills]
231+
[cursorLayoutKey, imageUrls.length, screenWidth, selectedSkills]
238232
);
239233
useTerminalFocusReporting(stdout, !disabled);
240234
useTerminalExtendedKeys(stdout, !disabled);
@@ -435,11 +429,6 @@ export const PromptInput = React.memo(function PromptInput({
435429
const returnAction = getPromptReturnKeyAction(key);
436430
const isPlainReturn = returnAction === "submit";
437431

438-
if (key.shift && key.tab) {
439-
onPlanModeChange(!planMode);
440-
return;
441-
}
442-
443432
if (showFileMentionMenu) {
444433
if (key.upArrow || key.downArrow || key.tab || returnAction === "submit") {
445434
return;
@@ -689,11 +678,6 @@ export const PromptInput = React.memo(function PromptInput({
689678
setShowModelDropdown(true);
690679
return;
691680
}
692-
if (item.kind === "plan") {
693-
clearSlashToken();
694-
onPlanModeChange(true);
695-
return;
696-
}
697681
if (item.kind === "raw") {
698682
clearSlashToken();
699683
setOpenRawModelDropdown(true);
@@ -724,6 +708,16 @@ export const PromptInput = React.memo(function PromptInput({
724708
resetPromptInput();
725709
return;
726710
}
711+
if (item.kind === "compact") {
712+
onSubmit({ text: "/compact", imageUrls: [], command: "compact" });
713+
resetPromptInput();
714+
return;
715+
}
716+
if (item.kind === "context") {
717+
onSubmit({ text: "/context", imageUrls: [], command: "context" });
718+
resetPromptInput();
719+
return;
720+
}
727721
if (item.kind === "mcp") {
728722
onSubmit({ text: "/mcp", imageUrls: [], command: "mcp" });
729723
resetPromptInput();
@@ -760,7 +754,6 @@ export const PromptInput = React.memo(function PromptInput({
760754
text: expandPasteMarkers(buffer.text, pastesRef.current),
761755
imageUrls,
762756
selectedSkills,
763-
planMode,
764757
});
765758
resetPromptInput();
766759
}
@@ -798,12 +791,6 @@ export const PromptInput = React.memo(function PromptInput({
798791
<Text dimColor> (use /skills to edit)</Text>
799792
</Box>
800793
) : null}
801-
{planMode ? (
802-
<Box width={screenWidth} justifyContent="flex-end">
803-
<Text color="yellow">💡 Plan mode</Text>
804-
<Text dimColor> (shift+tab to cycle)</Text>
805-
</Box>
806-
) : null}
807794
{/* Input */}
808795
<Box
809796
width={screenWidth}

packages/core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vegamo/deepcode-core",
3-
"version": "0.1.34",
3+
"version": "0.1.33",
44
"description": "Deep Code core library — LLM session management, tool execution, and shared utilities",
55
"license": "MIT",
66
"type": "module",
@@ -33,6 +33,7 @@
3333
"gray-matter": "^4.0.3",
3434
"ignore": "^7.0.5",
3535
"openai": "^6.35.0",
36+
"sql.js": "^1.14.1",
3637
"undici": "^7.25.0",
3738
"zod": "^4.4.3"
3839
}

0 commit comments

Comments
 (0)