Skip to content

Commit 2b50971

Browse files
committed
fix: the -p/--prompt option causes duplicated LLM calls when using /new.
1 parent 809f82d commit 2b50971

5 files changed

Lines changed: 52 additions & 33 deletions

File tree

src/cli.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function extractInitialPrompt(args: string[]): string | undefined {
6060
return undefined;
6161
}
6262

63-
const initialPrompt = extractInitialPrompt(args);
63+
let initialPrompt = extractInitialPrompt(args);
6464
const projectRoot = process.cwd();
6565
configureWindowsShell();
6666

@@ -78,11 +78,13 @@ async function main(): Promise<void> {
7878

7979
function startApp(): void {
8080
let restarting = false;
81+
const appInitialPrompt = initialPrompt;
82+
initialPrompt = undefined;
8183
const inkInstance = render(
8284
<App
8385
projectRoot={projectRoot}
8486
version={packageInfo.version}
85-
initialPrompt={initialPrompt}
87+
initialPrompt={appInitialPrompt}
8688
onRestart={() => restartRef.current?.()}
8789
/>,
8890
{ exitOnCtrlC: false }

src/session.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { logOpenAIChatCompletionDebug, normalizeDebugError } from "./common/debu
1818

1919
const MAX_SESSION_ENTRIES = 50;
2020
const DEFAULT_NEW_PROMPT_API_URL = "https://deepcode.vegamo.cn/api/plugin/new";
21+
const NEW_PROMPT_REPORT_TIMEOUT_MS = 3000;
2122
const DEFAULT_COMPACT_PROMPT_TOKEN_THRESHOLD = 128 * 1024;
2223
const DEEPSEEK_V4_COMPACT_PROMPT_TOKEN_THRESHOLD = 512 * 1024;
2324

@@ -1305,26 +1306,20 @@ ${skillMd}
13051306
return;
13061307
}
13071308

1309+
const controller = new AbortController();
1310+
const timeout = setTimeout(() => controller.abort(), NEW_PROMPT_REPORT_TIMEOUT_MS);
1311+
13081312
void fetch(DEFAULT_NEW_PROMPT_API_URL, {
13091313
method: "POST",
13101314
headers: {
13111315
"Content-Type": "application/json",
13121316
Token: machineId,
13131317
},
13141318
body: JSON.stringify({}),
1319+
signal: controller.signal,
13151320
})
1316-
.then(async (response) => {
1317-
if (response.ok) {
1318-
return;
1319-
}
1320-
1321-
const body = await response.text().catch(() => "");
1322-
throw new Error(`New prompt API request failed with status ${response.status}${body ? `: ${body}` : ""}`);
1323-
})
1324-
.catch((error) => {
1325-
const message = error instanceof Error ? error.message : String(error);
1326-
console.warn(`Failed to report new prompt: ${message}`);
1327-
});
1321+
.catch(() => {})
1322+
.finally(() => clearTimeout(timeout));
13281323
}
13291324

13301325
interruptActiveSession(): void {

src/tests/session.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as path from "path";
66
import { SessionManager, type SessionMessage } from "../session";
77

88
const originalFetch = globalThis.fetch;
9+
const originalConsoleWarn = console.warn;
910
const originalHome = process.env.HOME;
1011
const originalUserProfile = process.env.USERPROFILE;
1112
const tempDirs: string[] = [];
@@ -20,6 +21,7 @@ function setHomeDir(dir: string): void {
2021

2122
afterEach(() => {
2223
globalThis.fetch = originalFetch;
24+
console.warn = originalConsoleWarn;
2325
if (originalHome === undefined) {
2426
delete process.env.HOME;
2527
} else {
@@ -688,6 +690,7 @@ test("createSession reports a new prompt with the machineId token", async () =>
688690
assert.equal(fetchCalls.length, 1);
689691
assert.equal(String(fetchCalls[0].input), "https://deepcode.vegamo.cn/api/plugin/new");
690692
assert.equal(fetchCalls[0].init?.method, "POST");
693+
assert.ok(fetchCalls[0].init?.signal instanceof AbortSignal);
691694
assert.deepEqual(JSON.parse(String(fetchCalls[0].init?.body)), {});
692695
assert.equal((fetchCalls[0].init?.headers as Record<string, string>).Token, "machine-id-123");
693696
});
@@ -719,10 +722,33 @@ test("replySession reports a new prompt with the machineId token", async () => {
719722
assert.equal(fetchCalls.length, 1);
720723
assert.equal(String(fetchCalls[0].input), "https://deepcode.vegamo.cn/api/plugin/new");
721724
assert.equal(fetchCalls[0].init?.method, "POST");
725+
assert.ok(fetchCalls[0].init?.signal instanceof AbortSignal);
722726
assert.deepEqual(JSON.parse(String(fetchCalls[0].init?.body)), {});
723727
assert.equal((fetchCalls[0].init?.headers as Record<string, string>).Token, "machine-id-456");
724728
});
725729

730+
test("reporting a new prompt does not warn when the background request fails", async () => {
731+
const workspace = createTempDir("deepcode-report-failure-workspace-");
732+
const home = createTempDir("deepcode-report-failure-home-");
733+
setHomeDir(home);
734+
735+
const warnings: unknown[][] = [];
736+
console.warn = (...args: unknown[]) => {
737+
warnings.push(args);
738+
};
739+
globalThis.fetch = (async () => {
740+
throw new Error("fetch failed");
741+
}) as typeof fetch;
742+
743+
const manager = createSessionManager(workspace, "machine-id-failure");
744+
(manager as any).activateSession = async () => {};
745+
746+
await manager.createSession({ text: "hello world" });
747+
await flushPromises();
748+
749+
assert.deepEqual(warnings, []);
750+
});
751+
726752
test("replySession continues without appending /continue as a user message", async () => {
727753
const workspace = createTempDir("deepcode-continue-workspace-");
728754
const home = createTempDir("deepcode-continue-home-");

src/ui/App.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function App({ projectRoot, version = "", initialPrompt, onRestart }: App
5353
const { exit } = useApp();
5454
const { stdout, write } = useStdout();
5555
const { columns } = useWindowSize();
56+
const initialPromptSubmittedRef = useRef(false);
5657
const [view, setView] = useState<View>("chat");
5758
const [busy, setBusy] = useState(false);
5859
const [skills, setSkills] = useState<SkillInfo[]>([]);
@@ -296,6 +297,19 @@ export function App({ projectRoot, version = "", initialPrompt, onRestart }: App
296297
[handlePrompt]
297298
);
298299

300+
useEffect(() => {
301+
if (initialPromptSubmittedRef.current || !initialPrompt || !initialPrompt.trim()) {
302+
return;
303+
}
304+
305+
initialPromptSubmittedRef.current = true;
306+
handleSubmit({
307+
text: initialPrompt,
308+
imageUrls: [],
309+
selectedSkills: undefined,
310+
});
311+
}, [handleSubmit, initialPrompt]);
312+
299313
const handleSelectSession = useCallback(
300314
async (sessionId: string) => {
301315
const currentSessionId = sessionManager.getActiveSessionId();
@@ -471,7 +485,6 @@ export function App({ projectRoot, version = "", initialPrompt, onRestart }: App
471485
promptHistory={promptHistory}
472486
busy={busy}
473487
loadingText={loadingText}
474-
initialPrompt={initialPrompt}
475488
onSubmit={handleSubmit}
476489
onModelConfigChange={handleModelConfigChange}
477490
onInterrupt={handleInterrupt}

src/ui/PromptInput.tsx

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ type Props = {
6060
loadingText?: string | null;
6161
disabled?: boolean;
6262
placeholder?: string;
63-
initialPrompt?: string;
6463
onSubmit: (submission: PromptSubmission) => void;
6564
onModelConfigChange: (selection: ModelConfigSelection) => string | Promise<string>;
6665
onInterrupt: () => void;
@@ -110,16 +109,13 @@ export const PromptInput = React.memo(function PromptInput({
110109
loadingText,
111110
disabled,
112111
placeholder,
113-
initialPrompt,
114112
onSubmit,
115113
onModelConfigChange,
116114
onInterrupt,
117115
}: Props): React.ReactElement {
118116
const { exit } = useApp();
119117
const { stdout } = useStdout();
120-
const [buffer, setBuffer] = useState<PromptBufferState>(() =>
121-
initialPrompt ? { text: initialPrompt, cursor: initialPrompt.length } : EMPTY_BUFFER
122-
);
118+
const [buffer, setBuffer] = useState<PromptBufferState>(EMPTY_BUFFER);
123119
const [imageUrls, setImageUrls] = useState<string[]>([]);
124120
const [selectedSkills, setSelectedSkills] = useState<SkillInfo[]>([]);
125121
const [statusMessage, setStatusMessage] = useState<string | null>(null);
@@ -196,19 +192,6 @@ export const PromptInput = React.memo(function PromptInput({
196192
setDraftBeforeHistory(null);
197193
}, [promptHistoryKey]);
198194

199-
// Auto-submit initial prompt provided via -p/--prompt CLI flag
200-
useEffect(() => {
201-
if (!initialPrompt || !initialPrompt.trim()) return;
202-
203-
onSubmit({
204-
text: initialPrompt,
205-
imageUrls: [],
206-
selectedSkills: undefined,
207-
});
208-
setBuffer(EMPTY_BUFFER);
209-
clearPromptUndoRedoState(undoRedoRef.current);
210-
}, []); // Only on mount
211-
212195
useTerminalInput(
213196
(input, key) => {
214197
if (key.focusIn) {

0 commit comments

Comments
 (0)