Skip to content

Commit bb71f46

Browse files
authored
fix(ui): preserve reset soft command args
Fixes #91316 Summary: - Preserve `/reset soft ...` arguments when Control UI dispatches the local reset command. - Reuse parsed slash-command semantics for reset confirmation detection. - Keep non-soft reset tails on the destructive confirmation path across whitespace and colon separators. Verification: - `node_modules/.bin/oxfmt --check --threads=1 ui/src/ui/app-chat.ts ui/src/ui/app-chat.test.ts` - `node scripts/run-oxlint.mjs ui/src/ui/app-chat.ts ui/src/ui/app-chat.test.ts` - `node scripts/run-vitest.mjs ui/src/ui/app-chat.test.ts --maxWorkers=1 -t 'reset soft|reset softish|typed /reset command dispatch'` - Autoreview clean - AWS Crabbox `run_fbaf31b3fff8` focused Node 24 regression proof - AWS Crabbox `run_eb3af5b92e42` remote `check:changed` - Exact-head CI green for `5dee6f488fd393cb2127fe152f0d3fd53ccc13d2`
1 parent a6aa84f commit bb71f46

2 files changed

Lines changed: 80 additions & 6 deletions

File tree

ui/src/ui/app-chat.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,78 @@ describe("handleSendChat", () => {
13801380
expect(host.chatMessage).toBe("");
13811381
});
13821382

1383+
it.each([
1384+
{
1385+
input: "/reset soft please reload system prompt",
1386+
expected: "/reset soft please reload system prompt",
1387+
},
1388+
{
1389+
input: "/reset\tsoft please reload system prompt",
1390+
expected: "/reset soft please reload system prompt",
1391+
},
1392+
{
1393+
input: "/reset\nsoft please reload system prompt",
1394+
expected: "/reset soft please reload system prompt",
1395+
},
1396+
{
1397+
input: "/reset: soft please reload system prompt",
1398+
expected: "/reset soft please reload system prompt",
1399+
},
1400+
])("preserves $input args and skips confirmation dialog", async ({ input, expected }) => {
1401+
const confirm = vi.fn(() => false);
1402+
vi.stubGlobal("confirm", confirm);
1403+
const request = vi.fn(async (method: string) => {
1404+
if (method === "chat.send") {
1405+
return { status: "started" };
1406+
}
1407+
throw new Error(`Unexpected request: ${method}`);
1408+
});
1409+
const host = makeHost({
1410+
client: { request } as unknown as ChatHost["client"],
1411+
chatMessage: input,
1412+
sessionKey: "agent:main",
1413+
});
1414+
1415+
await handleSendChat(host);
1416+
1417+
expect(confirm).not.toHaveBeenCalled();
1418+
const payload = findRequestPayload(
1419+
request as unknown as MockCallSource,
1420+
"chat.send",
1421+
"chat send payload",
1422+
);
1423+
expect(payload.sessionKey).toBe("agent:main");
1424+
expect(payload.message).toBe(expected);
1425+
expect(host.chatMessage).toBe("");
1426+
});
1427+
1428+
it.each([
1429+
"/reset softish please archive",
1430+
"/reset\tsoftish please archive",
1431+
"/reset\nsoftish please archive",
1432+
"/reset: softish please archive",
1433+
])("keeps %s on the hard-reset confirmation path", async (message) => {
1434+
const confirm = vi.fn(() => false);
1435+
vi.stubGlobal("confirm", confirm);
1436+
const request = vi.fn(async (method: string) => {
1437+
throw new Error(`Unexpected request: ${method}`);
1438+
});
1439+
const host = makeHost({
1440+
client: { request } as unknown as ChatHost["client"],
1441+
chatMessage: "keep this draft",
1442+
sessionKey: "agent:main",
1443+
});
1444+
1445+
await handleSendChat(host, message, {
1446+
confirmReset: true,
1447+
restoreDraft: true,
1448+
});
1449+
1450+
expect(confirm).toHaveBeenCalledTimes(1);
1451+
expect(request).not.toHaveBeenCalled();
1452+
expect(host.chatMessage).toBe("keep this draft");
1453+
});
1454+
13831455
it("records visible send timing phases for a normal chat send", async () => {
13841456
const request = vi.fn(async (method: string) => {
13851457
if (method === "chat.send") {

ui/src/ui/app-chat.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,15 +245,17 @@ export function isChatStopCommand(text: string) {
245245
}
246246

247247
function isChatResetCommand(text: string) {
248-
const trimmed = text.trim();
249-
if (!trimmed) {
248+
const parsed = parseSlashCommand(text);
249+
if (!parsed || (parsed.command.key !== "new" && parsed.command.key !== "reset")) {
250250
return false;
251251
}
252-
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
253-
if (normalized === "/new" || normalized === "/reset") {
252+
if (parsed.command.key === "new") {
254253
return true;
255254
}
256-
return normalized.startsWith("/new ") || normalized.startsWith("/reset ");
255+
if (/^soft(?:\s|$)/.test(normalizeLowercaseStringOrEmpty(parsed.args))) {
256+
return false;
257+
}
258+
return true;
257259
}
258260

259261
function confirmChatResetCommand(text: string) {
@@ -1888,7 +1890,7 @@ async function dispatchSlashCommand(
18881890
await host.onSlashAction("new-session");
18891891
return;
18901892
case "reset":
1891-
await sendChatMessageNow(host, "/reset", {
1893+
await sendChatMessageNow(host, args ? `/reset ${args}` : "/reset", {
18921894
refreshSessions: true,
18931895
previousDraft: sendOpts?.previousDraft,
18941896
restoreDraft: sendOpts?.restoreDraft,

0 commit comments

Comments
 (0)